Skip to content
Open
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
12 changes: 5 additions & 7 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2495,6 +2495,11 @@ pub mod parse_worker {
let mut opts = ParserOptions::init(
crate::transpiler::to_parser_jsx_pragma(task.jsx.clone()),
loader,
js_parser::TsconfigOptions {
experimental_decorators: task.experimental_decorators,
emit_decorator_metadata: task.emit_decorator_metadata,
use_define_for_class_fields: task.use_define_for_class_fields,
},
);
opts.bundle = true;
opts.warn_about_unbundled_modules = false;
Expand Down Expand Up @@ -2539,13 +2544,6 @@ pub mod parse_worker {
opts.features.minify_identifiers = topts.minify_identifiers;
opts.features.minify_keep_names = topts.keep_names;
opts.features.minify_whitespace = topts.minify_whitespace;
opts.use_define_for_class_fields = task.use_define_for_class_fields;
opts.features.emit_decorator_metadata = task.emit_decorator_metadata;
// emitDecoratorMetadata implies legacy/experimental decorators, as it only
// makes sense with TypeScript's legacy decorator system (reflect-metadata).
// TC39 standard decorators have their own metadata mechanism.
opts.features.standard_decorators = !loader.is_typescript()
|| !(task.experimental_decorators || task.emit_decorator_metadata);
opts.features.unwrap_commonjs_packages = topts.unwrap_commonjs_packages;
opts.features.no_macros = topts.no_macros;
// Modeled as
Expand Down
1 change: 1 addition & 0 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7614,6 +7614,7 @@ pub mod bv2_impl {
let mut js_parser_options = bun_js_parser::ParserOptions::init(
self.transpiler_for_target(target).options.jsx.clone(),
Loader::Html,
bun_js_parser::TsconfigOptions::NONE,
);
js_parser_options.bundle = true;

Expand Down
11 changes: 5 additions & 6 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1490,12 +1490,11 @@ impl<'a> Transpiler<'a> {
is_entry_point: false,
};

opts.features.emit_decorator_metadata = this_parse.emit_decorator_metadata;
// emitDecoratorMetadata implies legacy/experimental decorators, as it only
// makes sense with TypeScript's legacy decorator system (reflect-metadata).
// TC39 standard decorators have their own metadata mechanism.
opts.features.standard_decorators = !loader.is_typescript()
|| !(this_parse.experimental_decorators || this_parse.emit_decorator_metadata);
opts.set_tsconfig(js_ast::TsconfigOptions {
experimental_decorators: this_parse.experimental_decorators,
emit_decorator_metadata: this_parse.emit_decorator_metadata,
use_define_for_class_fields: this_parse.use_define_for_class_fields,
});
opts.features.allow_runtime = self.options.allow_runtime;
opts.features.set_breakpoint_on_first_line =
this_parse.set_breakpoint_on_first_line;
Expand Down
2 changes: 1 addition & 1 deletion src/js_parser/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub mod typescript;
pub mod visit;

pub use p::P;
pub use parse::parse_entry::{Options as ParserOptions, Parser};
pub use parse::parse_entry::{Options as ParserOptions, Parser, TsconfigOptions};

// Full impl lives in *_jsc; this stub re-exposes the JSC-free constants and a
// placeholder `MacroContext` so lower-tier crates (bundler, transpiler) that
Expand Down
43 changes: 40 additions & 3 deletions src/js_parser/parse/parse_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,13 @@ pub fn parse_only_text<R>(
let mut ast_memory_allocator = bun_ast::ASTMemoryAllocator::borrowing(&arena);
let _ast_scope = ast_memory_allocator.enter();
let source = bun_ast::Source::init_path_string(&b"chunk.js"[..], text);
let mut options = Options::init(Default::default(), bun_ast::Loader::Js);
let mut options = Options::init(
Default::default(),
bun_ast::Loader::Js,
TsconfigOptions::NONE,
);
// JavaScriptCore has no decorator or auto-accessor grammar.
options.features.standard_decorators = false;
options.features.no_macros = true;
options.features.top_level_await = top_level_await;
options.jsc_builtin_syntax = jsc_builtin_syntax;
Expand Down Expand Up @@ -198,6 +204,23 @@ pub struct Options<'a> {
pub is_entry_point: bool,
}

/// The parser options that a tsconfig.json controls, besides JSX.
#[derive(Clone, Copy)]
pub struct TsconfigOptions {
pub experimental_decorators: bool,
pub emit_decorator_metadata: bool,
pub use_define_for_class_fields: bool,
}

impl TsconfigOptions {
/// No tsconfig.json governs this parse.
pub const NONE: TsconfigOptions = TsconfigOptions {
experimental_decorators: false,
emit_decorator_metadata: false,
use_define_for_class_fields: true,
};
}

impl<'a> Default for Options<'a> {
fn default() -> Self {
// `macro_context` is `None`; caller must set
Expand Down Expand Up @@ -362,7 +385,20 @@ impl<'a> Options<'a> {
self.bundle
}

pub fn init(jsx: options::JSX::Pragma, loader: options::Loader) -> Options<'static> {
/// `emitDecoratorMetadata` implies the legacy decorators: TypeScript emits it only for them.
#[inline]
pub fn set_tsconfig(&mut self, tsconfig: TsconfigOptions) {
self.use_define_for_class_fields = tsconfig.use_define_for_class_fields;
self.features.emit_decorator_metadata = tsconfig.emit_decorator_metadata;
self.features.standard_decorators =
!self.ts || !(tsconfig.experimental_decorators || tsconfig.emit_decorator_metadata);
}

pub fn init(
jsx: options::JSX::Pragma,
loader: options::Loader,
tsconfig: TsconfigOptions,
) -> Options<'static> {
// `macro_context` is `None`
// (see field comment); caller overwrites before use.
let mut opts = Options {
Expand All @@ -372,7 +408,7 @@ impl<'a> Options<'a> {
ignore_dce_annotations: false,
keep_directives: false,
preserve_unused_imports_ts: false,
use_define_for_class_fields: true,
use_define_for_class_fields: tsconfig.use_define_for_class_fields,
suppress_warnings_about_weird_code: true,
features: RuntimeFeatures::default(),
tree_shaking: false,
Expand All @@ -397,6 +433,7 @@ impl<'a> Options<'a> {
is_entry_point: false,
};
opts.jsx.parse = loader.is_jsx();
opts.set_tsconfig(tsconfig);
opts
}
}
Expand Down
39 changes: 26 additions & 13 deletions src/js_parser/parse/parse_skip_typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,11 +634,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
TsIdentKind::Normal => {
if GET_METADATA {
let ident = self.lexer.identifier;
let find_result = self.find_symbol(bun_ast::Loc::EMPTY, ident)?;
// The scan pass binds no symbols: it marks the name as used.
let ref_ = if Self::TRACK_SYMBOL_USAGE_DURING_PARSE_PASS {
self.store_name_in_ref(ident)
} else {
self.find_symbol(bun_ast::Loc::EMPTY, ident)?.r#ref
};
**result
.as_mut()
.expect("infallible: GET_METADATA implies Some") =
Metadata::MIdentifier(find_result.r#ref);
Metadata::MIdentifier(ref_);
}

self.lexer.next()?;
Expand Down Expand Up @@ -904,23 +909,31 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let r = result
.as_deref_mut()
.expect("infallible: GET_METADATA implies Some");
match r {
Metadata::MIdentifier(id_ref) => {
let id_ref = *id_ref;
let mut dot: Vec<Ref> = Vec::with_capacity(2);
dot.push(id_ref);
let find_result = self.find_symbol(bun_ast::Loc::EMPTY, ident)?;
dot.push(find_result.r#ref);
*r = Metadata::MDot(dot);
if Self::TRACK_SYMBOL_USAGE_DURING_PARSE_PASS {
// The scan pass prints no metadata, so it builds no `MDot`.
if matches!(r, Metadata::MIdentifier(_)) {
self.store_name_in_ref(ident);
Comment thread
robobun marked this conversation as resolved.
}
Metadata::MDot(dot) => {
if self.lexer.is_identifier_or_keyword() {
} else {
match r {
Metadata::MIdentifier(id_ref) => {
let id_ref = *id_ref;
let mut dot: Vec<Ref> = Vec::with_capacity(2);
dot.push(id_ref);
let find_result =
self.find_symbol(bun_ast::Loc::EMPTY, ident)?;
dot.push(find_result.r#ref);
*r = Metadata::MDot(dot);
}
Metadata::MDot(dot) => {
if self.lexer.is_identifier_or_keyword() {
let find_result =
self.find_symbol(bun_ast::Loc::EMPTY, ident)?;
dot.push(find_result.r#ref);
}
}
_ => {}
}
_ => {}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/js_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ pub mod options {
pub import_source: Cow<'static, [u8]>,
}
}
pub use crate::parse::parse_entry::{Options as ParserOptions, Parser};
pub use crate::parse::parse_entry::{Options as ParserOptions, Parser, TsconfigOptions};
pub use crate::renamer;
pub use crate::scan::scan_side_effects::SideEffects;

Expand Down
15 changes: 13 additions & 2 deletions src/runtime/api/JSTranspiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1555,12 +1555,23 @@ impl JSTranspiler {
let _ast_scope = ast_memory_allocator.enter();

let source = bun_ast::Source::init_path_string(loader.stdin_name(), code);
let jsx = match self.config.get().tsconfig.as_deref() {
let tsconfig = self.config.get().tsconfig.as_deref();
let jsx = match tsconfig {
Some(ts) => ts.merge_jsx(self.transpiler.get().options.jsx.clone()),
None => self.transpiler.get().options.jsx.clone(),
};

let mut opts = bun_js_parser::ParserOptions::init(jsx, loader);
let mut opts = bun_js_parser::ParserOptions::init(
jsx,
loader,
bun_js_parser::TsconfigOptions {
experimental_decorators: tsconfig.is_some_and(|ts| ts.experimental_decorators),
emit_decorator_metadata: tsconfig.is_some_and(|ts| ts.emit_decorator_metadata),
use_define_for_class_fields: tsconfig
.and_then(|ts| ts.use_define_for_class_fields)
.unwrap_or(true),
},
);
// SAFETY: see `transpiler_mut`. The `&mut Transpiler` is reborrowed
// disjointly for `macro_context` (stored in `opts`) and `options.define`
// (raw-addr read) below; both end when `opts` is consumed by `scan()`.
Expand Down
11 changes: 9 additions & 2 deletions src/runtime/cli/pm_diff_normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,15 @@ fn parse_js<'a>(
loader: bun_ast::Loader,
options: Options,
) -> Option<Box<bun_ast::Ast<'a>>> {
let mut opts = bun_js_parser::ParserOptions::init(Default::default(), loader);
let mut opts = bun_js_parser::ParserOptions::init(
Default::default(),
loader,
// No tsconfig comes with a package file: TypeScript gets the legacy decorators.
bun_js_parser::TsconfigOptions {
experimental_decorators: true,
..bun_js_parser::TsconfigOptions::NONE
},
);
// Print what is there: no dead-code removal, no macro execution, no import trimming.
opts.features.dead_code_elimination = options.dce;
opts.features.no_macros = true;
Expand All @@ -314,7 +322,6 @@ fn parse_js<'a>(
opts.keep_directives = true;
// Accept the same syntax as the runtime.
opts.features.top_level_await = true;
opts.features.standard_decorators = !loader.is_typescript();
opts.transform_only = true;
opts.suppress_warnings_about_weird_code = true;
let define: &'a bun_js_parser::Define = arena.alloc(bun_js_parser::Define::default());
Expand Down
6 changes: 6 additions & 0 deletions src/runtime/cli/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,12 @@ impl<'a> Repl<'a> {
let mut opts = bun_js_parser::ParserOptions::init(
vm.transpiler.options.jsx.clone(),
bun_ast::Loader::Tsx,
bun_js_parser::TsconfigOptions {
// The REPL transform below supports only the legacy decorators.
experimental_decorators: true,
emit_decorator_metadata: false,
use_define_for_class_fields: vm.transpiler.options.use_define_for_class_fields,
},
);
opts.repl_mode = true;
opts.features.dead_code_elimination = false; // REPL needs all code
Expand Down
12 changes: 9 additions & 3 deletions src/runtime/test_runner/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,11 @@ impl Snapshots {
// SAFETY: VM is thread-local singleton installed before any test runs; lives for the
// duration of the runner. Per `VirtualMachine::get` doc, callers form a short-lived borrow.
let vm = VirtualMachine::get().as_mut();
let opts =
js_parser::ParserOptions::init(vm.transpiler.options.jsx.clone(), bun_ast::Loader::Js);
let opts = js_parser::ParserOptions::init(
vm.transpiler.options.jsx.clone(),
bun_ast::Loader::Js,
js_parser::TsconfigOptions::NONE,
);
// Thread a per-call arena — js_parser is bump-allocated.
let arena = bun_alloc::Arena::new();
let mut temp_log = bun_ast::Log::init();
Expand Down Expand Up @@ -551,10 +554,13 @@ impl Snapshots {
}
lexer.next()?;
// `ParserOptions` isn't `Clone`; rebuild per-iteration.
let opts = js_parser::ParserOptions::init(
let mut opts = js_parser::ParserOptions::init(
vm.transpiler.options.jsx.clone(),
bun_ast::Loader::Js,
js_parser::TsconfigOptions::NONE,
);
// The arguments are parsed as TypeScript, with the legacy decorators.
opts.features.standard_decorators = false;
// `P::init` takes an out-param
// since 9a98701c980c — `P` is ~5 KiB and the previous
// `let p = P::init(..)?` shape forced 2-3 by-value moves.
Expand Down
8 changes: 7 additions & 1 deletion src/sourcemap_jsc/CodeCoverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,13 @@ fn print_for_coverage(path: &[u8], text: &[u8]) -> Option<bun_core::MutableStrin
let _ast_scope = ast_memory_allocator.enter();

let source = bun_ast::Source::init_path_string(path, text);
let mut options = bun_js_parser::ParserOptions::init(Default::default(), bun_ast::Loader::Js);
let mut options = bun_js_parser::ParserOptions::init(
Default::default(),
bun_ast::Loader::Js,
bun_js_parser::TsconfigOptions::NONE,
);
// JavaScriptCore has no decorator or auto-accessor grammar.
options.features.standard_decorators = false;
options.features.dead_code_elimination = false;
options.features.no_macros = true;
options.features.top_level_await = true;
Expand Down
Loading
Loading