Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4657,7 +4657,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
match &mut binding.data {
js_ast::b::B::BMissing(_) => {}
js_ast::b::B::BIdentifier(bind) => {
if !opts.is_typescript_declare || (opts.is_namespace_scope && opts.is_export) {
if !opts.is_typescript_declare || (opts.scope.is_namespace() && opts.is_export) {
bind.r#ref = self.declare_symbol(
kind,
binding.loc,
Expand Down
2 changes: 1 addition & 1 deletion src/js_parser/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if Self::IS_TYPESCRIPT_ENABLED {
if opts.is_typescript_declare {
p.pop_and_discard_scope(scope_index);
if opts.is_namespace_scope && opts.is_export {
if opts.scope.is_namespace() && opts.is_export {
p.has_non_local_export_declare_inside_namespace = true;
}

Expand Down
7 changes: 4 additions & 3 deletions src/js_parser/parse/parse_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use crate::defines::Define;
use crate::lexer as js_lexer;
use crate::p::P;
use crate::parser::{
Jest, ParseStatementOptions, RuntimeFeatures, RuntimeImports, ScanPassResult, WrapMode,
Jest, ParseStatementOptions, RuntimeFeatures, RuntimeImports, ScanPassResult, StatementScope,
WrapMode,
};
use bun_ast as js_ast;
use bun_ast::DeclaredSymbol;
Expand Down Expand Up @@ -414,7 +415,7 @@ impl<'a> Parser<'a> {

// Parse the file in the first pass, but do not bind symbols
let mut opts = ParseStatementOptions {
is_module_scope: true,
scope: StatementScope::Module,
..Default::default()
};

Expand Down Expand Up @@ -695,7 +696,7 @@ impl<'a> Parser<'a> {

// Parse the file in the first pass, but do not bind symbols
let mut opts = ParseStatementOptions {
is_module_scope: true,
scope: StatementScope::Module,
..Default::default()
};
let mut parse_tracer = bun_core::perf::trace("JSParser::parse");
Expand Down
2 changes: 1 addition & 1 deletion src/js_parser/parse/parse_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
p.pop_and_discard_scope(if_stmt_scope_index);
}

if opts.is_typescript_declare && opts.is_namespace_scope && opts.is_export {
if opts.is_typescript_declare && opts.scope.is_namespace() && opts.is_export {
p.has_non_local_export_declare_inside_namespace = true;
}

Expand Down
4 changes: 2 additions & 2 deletions src/js_parser/parse/parse_skip_typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1332,7 +1332,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let name = self.lexer.identifier;
self.lexer.expect(T::TIdentifier)?;

if opts.is_module_scope {
if opts.scope.is_module() {
self.local_type_names.put(name, true)?;
}

Expand All @@ -1354,7 +1354,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let name = self.lexer.identifier;
self.lexer.expect(T::TIdentifier)?;

if opts.is_module_scope {
if opts.scope.is_module() {
self.local_type_names.put(name, true)?;
}

Expand Down
63 changes: 25 additions & 38 deletions src/js_parser/parse/parse_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use js_lexer::T;
use crate::parser::fs;
use crate::parser::{
AwaitOrYield, DeferredTsDecorators, LexicalDecl, ParseStatementOptions, ParsedPath, Ref,
StmtList,
StatementScope, StmtList,
};
use crate::typescript;
use bun_ast::{ImportKind, ImportRecordFlags, ImportRecordTag};
Expand Down Expand Up @@ -828,11 +828,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
loc: bun_ast::Loc,
) -> Result<Stmt> {
let previous_export_keyword = p.esm_export_keyword;
if opts.is_module_scope {
p.esm_export_keyword = p.lexer.range();
} else if !opts.is_namespace_scope {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
match opts.scope {
StatementScope::Module => p.esm_export_keyword = p.lexer.range(),
StatementScope::Namespace => {}
StatementScope::Nested => {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
}
p.lexer.next()?;

Expand Down Expand Up @@ -860,8 +862,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

T::TImport => {
// "export import foo = bar"
if Self::IS_TYPESCRIPT_ENABLED && (opts.is_module_scope || opts.is_namespace_scope)
{
if Self::IS_TYPESCRIPT_ENABLED && opts.scope != StatementScope::Nested {
opts.is_export = true;
return p.parse_stmt(opts);
}
Expand Down Expand Up @@ -931,7 +932,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
return Err(crate::Error::SyntaxError);
}
let mut skipper = ParseStatementOptions {
is_module_scope: opts.is_module_scope,
scope: opts.scope,
is_export: true,
..Default::default()
};
Expand Down Expand Up @@ -964,9 +965,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}

T::TDefault => {
if !opts.is_module_scope
&& (!opts.is_namespace_scope || !opts.is_typescript_declare)
{
if !opts.allows_esm_import_export() {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
Expand Down Expand Up @@ -1193,9 +1192,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
))
}
T::TAsterisk => {
if !opts.is_module_scope
&& (!opts.is_namespace_scope || !opts.is_typescript_declare)
{
if !opts.allows_esm_import_export() {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
Expand Down Expand Up @@ -1275,9 +1272,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
))
}
T::TOpenBrace => {
if !opts.is_module_scope
&& (!opts.is_namespace_scope || !opts.is_typescript_declare)
{
if !opts.allows_esm_import_export() {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
Expand Down Expand Up @@ -1407,7 +1402,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let mut was_originally_bare_import = false;

// "export import foo = bar"
if (opts.is_export || (opts.is_namespace_scope && !opts.is_typescript_declare))
if (opts.is_export || (opts.scope.is_namespace() && !opts.is_typescript_declare))
&& p.lexer.token != T::TIdentifier
{
p.lexer.expected(T::TIdentifier)?;
Expand All @@ -1431,19 +1426,15 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
T::TStringLiteral | T::TNoSubstitutionTemplateLiteral => {
// "import 'path'"
if !opts.is_module_scope
&& (!opts.is_namespace_scope || !opts.is_typescript_declare)
{
if !opts.allows_esm_import_export() {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
was_originally_bare_import = true;
}
T::TAsterisk => {
// "import * as ns from 'path'"
if !opts.is_module_scope
&& (!opts.is_namespace_scope || !opts.is_typescript_declare)
{
if !opts.allows_esm_import_export() {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
Expand All @@ -1461,9 +1452,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
T::TOpenBrace => {
// "import {item1, item2} from 'path'"
if !opts.is_module_scope
&& (!opts.is_namespace_scope || !opts.is_typescript_declare)
{
if !opts.allows_esm_import_export() {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
Expand Down Expand Up @@ -1491,7 +1480,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
T::TIdentifier => {
// "import defaultItem from 'path'"
// "import foo = bar"
if !opts.is_module_scope && !opts.is_namespace_scope {
if opts.scope == StatementScope::Nested {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
Expand Down Expand Up @@ -1528,9 +1517,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// Same scope restriction as `import * as ns from 'path'`:
// ESM import declarations are only valid at module scope
// (or inside a TypeScript `declare namespace`).
if !opts.is_module_scope
&& (!opts.is_namespace_scope || !opts.is_typescript_declare)
{
if !opts.allows_esm_import_export() {
p.lexer.unexpected()?;
return Err(crate::Error::SyntaxError);
}
Expand Down Expand Up @@ -1606,7 +1593,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// Parse TypeScript import assignment statements
if p.lexer.token == T::TEquals
|| opts.is_export
|| (opts.is_namespace_scope && !opts.is_typescript_declare)
|| (opts.scope.is_namespace() && !opts.is_typescript_declare)
{
p.esm_import_keyword = previous_import_keyword; // This wasn't an ESM import statement after all;
return p.parse_type_script_import_equals_stmt(
Expand Down Expand Up @@ -1778,7 +1765,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if p.lexer.token == T::TIdentifier && !p.lexer.has_newline_before {
// "type Foo = any"
let mut stmt_opts = ParseStatementOptions {
is_module_scope: opts.is_module_scope,
scope: opts.scope,
..Default::default()
};
p.skip_type_script_type_stmt(&mut stmt_opts)?;
Expand All @@ -1792,7 +1779,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// "declare module 'fs' {}"
// "declare module 'fs';"
if !p.lexer.has_newline_before
&& (opts.is_module_scope || opts.is_namespace_scope)
&& opts.scope != StatementScope::Nested
&& (p.lexer.token == T::TIdentifier
|| (p.lexer.token == T::TStringLiteral && opts.is_typescript_declare))
{
Expand All @@ -1805,7 +1792,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// "export default interface \n Foo {}"
if !p.lexer.has_newline_before || opts.is_name_optional {
let mut stmt_opts = ParseStatementOptions {
is_module_scope: opts.is_module_scope,
scope: opts.scope,
..Default::default()
};

Expand Down Expand Up @@ -1836,7 +1823,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
js_lexer::TypescriptStmtKeyword::TsStmtGlobal => {
// "declare module 'fs' { global { namespace NodeJS {} } }"
if opts.is_namespace_scope
if opts.scope.is_namespace()
&& opts.is_typescript_declare
&& p.lexer.token == T::TOpenBrace
{
Expand Down Expand Up @@ -1934,7 +1921,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// inside a namespace with an "export var" statement containing all
// of the declared bindings. That "export var" statement will later
// cause identifiers to be transformed into property accesses.
if opts.is_namespace_scope && opts.is_export {
if opts.scope.is_namespace() && opts.is_export {
let mut decls: G::DeclList = bun_alloc::AstAlloc::vec();
match &stmt.data {
js_ast::StmtData::SLocal(local) => {
Expand Down
7 changes: 4 additions & 3 deletions src/js_parser/parse/parse_suffix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

// Remove unnecessary optional chains
if p.options.features.minify_syntax {
let result = SideEffects::to_null_or_undefined(p, &left.data);
if result.ok && !result.value {
optional_start = None;
if let Some(result) = SideEffects::to_null_or_undefined(p, &left.data) {
if !result.value {
optional_start = None;
}
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/js_parser/parse/parse_typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bun_collections::VecExt;
use crate::Error;
use crate::lexer::{self as js_lexer, T};
use crate::p::P;
use crate::parser::{FnOrArrowDataParse, ParseStatementOptions, Ref, ScopeOrder};
use crate::parser::{FnOrArrowDataParse, ParseStatementOptions, Ref, ScopeOrder, StatementScope};
use bun_alloc::{ArenaVec as BumpVec, ArenaVecExt as _};
use bun_ast::expr::EFlags;
use bun_ast::flags;
Expand Down Expand Up @@ -250,7 +250,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

let mut _opts = ParseStatementOptions {
is_export: true,
is_namespace_scope: true,
scope: StatementScope::Namespace,
is_typescript_declare: opts.is_typescript_declare,
..ParseStatementOptions::default()
};
Expand All @@ -263,7 +263,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
} else {
p.lexer.expect(T::TOpenBrace)?;
let mut _opts = ParseStatementOptions {
is_namespace_scope: true,
scope: StatementScope::Namespace,
is_typescript_declare: opts.is_typescript_declare,
..ParseStatementOptions::default()
};
Expand Down Expand Up @@ -398,7 +398,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
|| opts.is_typescript_declare
{
p.pop_and_discard_scope(scope_index);
if opts.is_module_scope {
if opts.scope.is_module() {
p.local_type_names.put(name_text, true)?;
}
return Ok(p.s(S::TypeScript {}, loc));
Expand Down Expand Up @@ -733,7 +733,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
p.lexer.expect(T::TCloseBrace)?;

if opts.is_typescript_declare {
if opts.is_namespace_scope && opts.is_export {
if opts.scope.is_namespace() && opts.is_export {
p.has_non_local_export_declare_inside_namespace = true;
}

Expand Down
35 changes: 33 additions & 2 deletions src/js_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1567,12 +1567,34 @@ pub struct ParseClassOptions<'a> {
pub(crate) is_type_script_declare: bool,
}

#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum StatementScope {
/// Any nested block/function body. ESM import/export are disallowed here.
#[default]
Nested,
/// Top-level module statement list.
Module,
/// Inside a TypeScript `namespace`/`module` block.
Namespace,
}

impl StatementScope {
#[inline]
pub(crate) fn is_module(self) -> bool {
matches!(self, Self::Module)
}
#[inline]
pub(crate) fn is_namespace(self) -> bool {
matches!(self, Self::Namespace)
}
}

#[derive(Default, Clone, Copy)]
pub struct ParseStatementOptions<'a> {
pub(crate) ts_decorators: Option<DeferredTsDecorators<'a>>,
pub(crate) lexical_decl: LexicalDecl,
pub(crate) is_module_scope: bool,
pub(crate) is_namespace_scope: bool,
pub(crate) scope: StatementScope,
pub(crate) is_export: bool,
pub(crate) is_using_statement: bool,
/// For "export default" pseudo-statements,
Expand All @@ -1588,6 +1610,15 @@ impl<'a> ParseStatementOptions<'a> {
};
!decs.values.is_empty()
}

#[inline]
pub(crate) fn allows_esm_import_export(&self) -> bool {
match self.scope {
StatementScope::Module => true,
StatementScope::Namespace => self.is_typescript_declare,
StatementScope::Nested => false,
}
}
}

// Only const-able AST node singletons live here. Callers needing the rest
Expand Down
Loading
Loading