Skip to content
Draft
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
1 change: 0 additions & 1 deletion oxlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
"test/bundler/native-plugin.test.ts", // parser doesn't handle import metadata
"test/bundler/transpiler/with-statement-works.js", // parser doesn't allow `with` statement
"test/js/node/module/extensions-fixture", // these files are not meant to be linted
"test/cli/run/module-type-fixture",

// TODO: fix these
"src/js/node/http2.ts",
Expand Down
86 changes: 86 additions & 0 deletions src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2524,6 +2524,92 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
Ok(())
}

/// [`generate_import_stmt`](Self::generate_import_stmt) as
/// `const { name: ref } = require(path)`, for a file that gets the runtime's
/// CommonJS function wrapper.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub(crate) fn generate_require_stmt<I, Sym>(
&mut self,
import_path: &'a [u8],
imports: I,
parts: &mut ListManaged<'a, js_ast::Part>,
symbols: &Sym,
tag: bun_ast::PartTag,
) -> Result<(), crate::Error>
where
I: AsRef<[<Sym as GenerateImportSymbols>::Key]>,
Sym: GenerateImportSymbols,
{
let imports = imports.as_ref();
let import_record_i =
self.add_import_record_by_range(ImportKind::Require, bun_ast::Range::NONE, import_path);

let mut declared_symbols = bun_ast::DeclaredSymbolList::default();
declared_symbols.ensure_total_capacity(imports.len())?;
let mut properties = BumpVec::<B::Property>::with_capacity_in(imports.len(), self.arena);
for alias in imports {
let ref_ = symbols.get(alias).expect("unreachable");
let alias_name: &'static [u8] = symbols.alias_name(alias);
let key = self.new_expr(
E::String {
data: alias_name.into(),
..Default::default()
},
bun_ast::Loc::EMPTY,
);
properties.push(B::Property {
flags: bun_ast::flags::PROPERTY_NONE,
key,
value: self.b(B::Identifier { r#ref: ref_ }, bun_ast::Loc::EMPTY),
default_value: None,
});
declared_symbols.append_assume_capacity(DeclaredSymbol {
ref_,
is_top_level: true,
});
}

let binding = self.b(
B::Object {
properties: bun_ast::StoreSlice::from_bump(properties),
is_single_line: true,
close_brace_loc: bun_ast::Loc::EMPTY,
},
bun_ast::Loc::EMPTY,
);
let value = self.new_expr(
E::RequireString {
import_record_index: import_record_i,
..Default::default()
},
bun_ast::Loc::EMPTY,
);
let mut decls = G::DeclList::init_capacity(1);
decls.append_assume_capacity(G::Decl {
binding,
value: Some(value),
});
let stmt = self.s(
S::Local {
kind: js_ast::LocalKind::KConst,
decls,
..Default::default()
},
bun_ast::Loc::EMPTY,
);

let is_jsx = tag == bun_ast::PartTag::JsxImport;
parts.push(js_ast::Part {
stmts: self.arena.alloc_slice_fill_with(1, |_| stmt).into(),
declared_symbols,
import_record_indices: js_ast::PartImportRecordIndices::init_one(import_record_i),
tag,
can_be_removed_if_unused: is_jsx,
force_tree_shaking: is_jsx,
..Default::default()
});
Comment thread
robobun marked this conversation as resolved.
Resolved
Ok(())
}

pub(crate) fn generate_react_refresh_import(
&mut self,
parts: &mut ListManaged<'a, js_ast::Part>,
Expand Down
210 changes: 83 additions & 127 deletions src/js_parser/parse/parse_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,14 @@ impl<'a> Options<'a> {
hasher.update(b"udfcf=0");
}

// The cache file is keyed by the content hash alone, and the hint
// changes the output format.
Comment thread
robobun marked this conversation as resolved.
Resolved
hasher.update(match self.module_type {
options::ModuleType::Unknown => b"mt=?",
options::ModuleType::Cjs => b"mt=c",
options::ModuleType::Esm => b"mt=e",
});

self.features.hash_for_runtime_transpiler(hasher);
}

Expand Down Expand Up @@ -2083,6 +2091,9 @@ impl<'a> Parser<'a> {
exports_kind = js_ast::ExportsKind::EsmWithDynamicFallbackFromCjs;
}

// An `import` inside the CommonJS function wrapper is a syntax error.
let wrap_in_commonjs = wrap_mode == WrapMode::BunCommonjs;

// Auto inject jest globals into the test file
'outer: {
if !p.options.features.inject_jest_globals {
Expand All @@ -2107,102 +2118,33 @@ impl<'a> Parser<'a> {
}

// if they didn't use any of the jest globals, don't inject it, I guess.
// Iterates the static `Jest::FIELDS`
// table (`&[(&'static str, fn(&Jest) -> Ref)]`); declaration order
// determines the emitted clause/property order.
let items_count: usize = {
let mut count: usize = 0;
for (_name, get_ref) in Jest::FIELDS {
count += (p.symbols.as_slice()[get_ref(&p.jest).inner_index() as usize]
.use_count_estimate
> 0) as usize;
// `Jest::FIELDS` order is the emitted clause/property order.
let mut used = [0u8; Jest::FIELDS.len()];
let mut items_count: usize = 0;
for (i, (_name, get_ref)) in Jest::FIELDS.iter().enumerate() {
if p.symbols.as_slice()[get_ref(&p.jest).inner_index() as usize].use_count_estimate
> 0
{
used[items_count] = i as u8;
items_count += 1;
}
count
};
}
if items_count == 0 {
break 'outer;
}

let mut declared_symbols = bun_ast::DeclaredSymbolList::default();
declared_symbols.ensure_total_capacity(items_count)?;

// For CommonJS modules, use require instead of import
if exports_kind == js_ast::ExportsKind::Cjs {
let import_record_id = p.add_import_record(
bun_ast::ImportKind::Require,
bun_ast::Loc::EMPTY,
if wrap_in_commonjs {
let jest = p.jest;
p.generate_require_stmt(
b"bun:test",
);

// Create object binding pattern for destructuring
let mut properties = BumpVec::<B::Property>::with_capacity_in(items_count, p.arena);
for (symbol_name, get_ref) in Jest::FIELDS {
let r = get_ref(&p.jest);
if p.symbols.as_slice()[r.inner_index() as usize].use_count_estimate > 0 {
let key = p.new_expr(
E::String {
data: symbol_name.as_bytes().into(),
..Default::default()
},
bun_ast::Loc::EMPTY,
);
let value = p.b(B::Identifier { r#ref: r }, bun_ast::Loc::EMPTY);
properties.push(B::Property {
flags: bun_ast::flags::PROPERTY_NONE,
key,
value,
default_value: None,
});
declared_symbols.append_assume_capacity(DeclaredSymbol {
ref_: r,
is_top_level: true,
});
}
}
let properties = bun_ast::StoreSlice::from_bump(properties);

// Create: const { test, expect, ... } = require("bun:test")
let binding = p.b(
B::Object {
properties,
is_single_line: false,
close_brace_loc: bun_ast::Loc::EMPTY,
},
bun_ast::Loc::EMPTY,
);
let value = p.new_expr(
E::RequireString {
import_record_index: import_record_id,
..Default::default()
},
bun_ast::Loc::EMPTY,
);
let mut decls = G::DeclList::init_capacity(1);
decls.append_assume_capacity(G::Decl {
binding,
value: Some(value),
});

let local_stmt = p.s(
S::Local {
kind: js_ast::LocalKind::KConst,
decls,
..Default::default()
},
bun_ast::Loc::EMPTY,
);
let part_stmts = p.arena.alloc_slice_fill_with(1, |_| local_stmt);

before.push(js_ast::Part {
stmts: part_stmts.into(),
declared_symbols,
import_record_indices: js_ast::PartImportRecordIndices::init_one(
import_record_id,
),
tag: bun_ast::PartTag::BunTest,
..Default::default()
});
&used[..items_count],
&mut before,
&jest,
bun_ast::PartTag::BunTest,
)?;
} else {
let mut declared_symbols = bun_ast::DeclaredSymbolList::default();
declared_symbols.ensure_total_capacity(items_count)?;
let import_record_id = p.add_import_record(
bun_ast::ImportKind::Stmt,
bun_ast::Loc::EMPTY,
Expand Down Expand Up @@ -2294,17 +2236,28 @@ impl<'a> Parser<'a> {
// never touches `self.runtime_imports`, so the clone is purely a
// borrow-checker workaround.
let symbols = p.runtime_imports.clone();
p.generate_import_stmt(
RuntimeImports::NAME,
&runtime_imports[0..i],
&mut before,
&symbols,
None,
b"import_",
true,
js_ast::PartTag::Runtime,
)
.expect("unreachable");
if wrap_in_commonjs {
p.generate_require_stmt(
RuntimeImports::NAME,
&runtime_imports[0..i],
&mut before,
&symbols,
js_ast::PartTag::Runtime,
)
.expect("unreachable");
} else {
p.generate_import_stmt(
RuntimeImports::NAME,
&runtime_imports[0..i],
&mut before,
&symbols,
None,
b"import_",
true,
js_ast::PartTag::Runtime,
)
.expect("unreachable");
}
}
}

Expand All @@ -2325,34 +2278,37 @@ impl<'a> Parser<'a> {

let mut buf: [&'static [u8]; 3] = [b"", b"", b""];
let runtime_import_names = jsx_imports.runtime_import_names(&mut buf);

if !runtime_import_names.is_empty() {
p.generate_import_stmt(
import_source,
runtime_import_names,
&mut before,
&jsx_imports,
None,
b"",
false,
js_ast::PartTag::JsxImport,
)
.expect("unreachable");
}

let source_import_names = jsx_imports.source_import_names();
if !source_import_names.is_empty() {
p.generate_import_stmt(
package_name,
source_import_names,
&mut before,
&jsx_imports,
None,
b"",
false,
js_ast::PartTag::JsxImport,
)
.expect("unreachable");

for (path, names) in [
(import_source, runtime_import_names),
(package_name, source_import_names),
] {
if names.is_empty() {
continue;
}
if wrap_in_commonjs {
p.generate_require_stmt(
path,
names,
&mut before,
&jsx_imports,
js_ast::PartTag::JsxImport,
)
.expect("unreachable");
} else {
p.generate_import_stmt(
path,
names,
&mut before,
&jsx_imports,
None,
b"",
false,
js_ast::PartTag::JsxImport,
)
.expect("unreachable");
}
}

p.jsx_imports = jsx_imports;
Expand Down
16 changes: 16 additions & 0 deletions src/js_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,21 @@ impl crate::p::GenerateImportSymbols for RuntimeImports {
}
}

impl crate::p::GenerateImportSymbols for Jest {
/// Index into [`Jest::FIELDS`].
type Key = u8;

#[inline]
fn get(&self, key: &u8) -> Option<Ref> {
Some((Jest::FIELDS[*key as usize].1)(self))
}

#[inline]
fn alias_name(&self, key: &u8) -> &'static [u8] {
Jest::FIELDS[*key as usize].0.as_bytes()
}
}

impl crate::p::GenerateImportSymbols for JSXImportSymbols {
type Key = &'static [u8];

Expand Down Expand Up @@ -1593,6 +1608,7 @@ impl<'a> MacroState<'a> {
}
}

#[derive(Clone, Copy)]
pub struct Jest {
pub(crate) test: Ref,
pub(crate) it: Ref,
Expand Down
Loading
Loading