diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000000..a21be158eba425 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,26 @@ +name: Lint + +on: + pull_request: + workflow_dispatch: + +env: + BUN_VERSION: "1.1.38" + OXLINT_VERSION: "0.15.0" + +jobs: + lint-js: + name: "Lint JavaScript" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Bun + uses: ./.github/actions/setup-bun + with: + bun-version: ${{ env.BUN_VERSION }} + - name: Lint + run: bunx oxlint --config oxlint.json --quiet --format github + + + + diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml new file mode 100644 index 00000000000000..74c2aaec89a2c6 --- /dev/null +++ b/.github/workflows/typos.yml @@ -0,0 +1,19 @@ +name: Typos + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Spellcheck + uses: crate-ci/typos@v1.29.4 + with: + files: docs/**/* diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 00000000000000..7819bc056fe4a8 --- /dev/null +++ b/.typos.toml @@ -0,0 +1,2 @@ +[type.md] +extend-ignore-words-re = ["^ba"] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1341d8149381b..03a2b4d663fc78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ $ brew install llvm@18 ```bash#Ubuntu/Debian $ # LLVM has an automatic installation script that is compatible with all versions of Ubuntu -$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 16 all +$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 18 all ``` ```bash#Arch @@ -71,23 +71,21 @@ $ sudo pacman -S llvm clang lld ``` ```bash#Fedora -$ sudo dnf install 'dnf-command(copr)' -$ sudo dnf copr enable -y @fedora-llvm-team/llvm17 -$ sudo dnf install llvm16 clang16 lld16-devel +$ sudo dnf install llvm18 clang18 lld18-devel ``` ```bash#openSUSE Tumbleweed -$ sudo zypper install clang16 lld16 llvm16 +$ sudo zypper install clang18 lld18 llvm18 ``` {% /codetabs %} -If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-16.0.6). +If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-18.1.8). Make sure Clang/LLVM 18 is in your path: ```bash -$ which clang-16 +$ which clang-18 ``` If not, run this to manually add it: @@ -96,13 +94,13 @@ If not, run this to manually add it: ```bash#macOS (Homebrew) # use fish_add_path if you're using fish -# use path+="$(brew --prefix llvm@16)/bin" if you are using zsh -$ export PATH="$(brew --prefix llvm@16)/bin:$PATH" +# use path+="$(brew --prefix llvm@18)/bin" if you are using zsh +$ export PATH="$(brew --prefix llvm@18)/bin:$PATH" ``` ```bash#Arch # use fish_add_path if you're using fish -$ export PATH="$PATH:/usr/lib/llvm16/bin" +$ export PATH="$PATH:/usr/lib/llvm18/bin" ``` {% /codetabs %} @@ -163,7 +161,7 @@ The binary will be located at `./build/release/bun` and `./build/release/bun-pro ### Download release build from pull requests -To save you time spent building a release build locally, we provide a way to run release builds from pull requests. This is useful for manully testing changes in a release build before they are merged. +To save you time spent building a release build locally, we provide a way to run release builds from pull requests. This is useful for manually testing changes in a release build before they are merged. To run a release build from a pull request, you can use the `bun-pr` npm package: @@ -240,7 +238,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable ``` The C++ compiler - "/usr/bin/clang++-16" + "/usr/bin/clang++-18" is not able to compile a simple test program. ``` diff --git a/bench/hot-module-reloading/css-stress-test/src/index.tsx b/bench/hot-module-reloading/css-stress-test/src/index.tsx index 5eefb430406aaa..c8b470ec7aa7ea 100644 --- a/bench/hot-module-reloading/css-stress-test/src/index.tsx +++ b/bench/hot-module-reloading/css-stress-test/src/index.tsx @@ -1,7 +1,7 @@ import ReactDOM from "react-dom"; import { Main } from "./main"; -const Base = ({}) => { +const Base = () => { const name = typeof location !== "undefined" ? decodeURIComponent(location.search.substring(1)) : null; return
; }; diff --git a/bench/snippets/urlsearchparams.mjs b/bench/snippets/urlsearchparams.mjs index 83a874dc5f191a..4663dbfedf2c33 100644 --- a/bench/snippets/urlsearchparams.mjs +++ b/bench/snippets/urlsearchparams.mjs @@ -10,7 +10,6 @@ bench("new URLSearchParams(obj)", () => { "Content-Length": "123", "User-Agent": "node-fetch/1.0", "Accept-Encoding": "gzip,deflate", - "Content-Length": "0", "Content-Range": "bytes 0-9/10", }); }); diff --git a/docs/api/s3.md b/docs/api/s3.md index 46484e7e552539..f467cd21723a8d 100644 --- a/docs/api/s3.md +++ b/docs/api/s3.md @@ -367,7 +367,7 @@ If the `S3_*` environment variable is not set, Bun will also check for the `AWS_ These environment variables are read from [`.env` files](/docs/runtime/env) or from the process environment at initialization time (`process.env` is not used for this). -These defaults are overriden by the options you pass to `s3(credentials)`, `new Bun.S3Client(credentials)`, or any of the methods that accept credentials. So if, for example, you use the same credentials for different buckets, you can set the credentials once in your `.env` file and then pass `bucket: "my-bucket"` to the `s3()` helper function without having to specify all the credentials again. +These defaults are overridden by the options you pass to `s3(credentials)`, `new Bun.S3Client(credentials)`, or any of the methods that accept credentials. So if, for example, you use the same credentials for different buckets, you can set the credentials once in your `.env` file and then pass `bucket: "my-bucket"` to the `s3()` helper function without having to specify all the credentials again. ### `S3Client` objects diff --git a/docs/api/sqlite.md b/docs/api/sqlite.md index 49c84136bb6de9..f9a707d27c30da 100644 --- a/docs/api/sqlite.md +++ b/docs/api/sqlite.md @@ -82,7 +82,7 @@ const strict = new Database( // throws error because of the typo: const query = strict .query("SELECT $message;") - .all({ messag: "Hello world" }); + .all({ message: "Hello world" }); const notStrict = new Database( ":memory:" @@ -90,7 +90,7 @@ const notStrict = new Database( // does not throw error: notStrict .query("SELECT $message;") - .all({ messag: "Hello world" }); + .all({ message: "Hello world" }); ``` ### Load via ES module import diff --git a/docs/api/utils.md b/docs/api/utils.md index 8c96472f01be70..979e4068511d4b 100644 --- a/docs/api/utils.md +++ b/docs/api/utils.md @@ -121,7 +121,7 @@ const id = randomUUIDv7(); A UUID v7 is a 128-bit value that encodes the current timestamp, a random value, and a counter. The timestamp is encoded using the lowest 48 bits, and the random value and counter are encoded using the remaining bits. -The `timestamp` parameter defaults to the current time in milliseconds. When the timestamp changes, the counter is reset to a psuedo-random integer wrapped to 4096. This counter is atomic and threadsafe, meaning that using `Bun.randomUUIDv7()` in many Workers within the same process running at the same timestamp will not have colliding counter values. +The `timestamp` parameter defaults to the current time in milliseconds. When the timestamp changes, the counter is reset to a pseudo-random integer wrapped to 4096. This counter is atomic and threadsafe, meaning that using `Bun.randomUUIDv7()` in many Workers within the same process running at the same timestamp will not have colliding counter values. The final 8 bytes of the UUID are a cryptographically secure random value. It uses the same random number generator used by `crypto.randomUUID()` (which comes from BoringSSL, which in turn comes from the platform-specific system random number generator usually provided by the underlying hardware). diff --git a/docs/cli/filter.md b/docs/cli/filter.md index 38a497548b5ab3..62c53658a3da1f 100644 --- a/docs/cli/filter.md +++ b/docs/cli/filter.md @@ -41,7 +41,7 @@ $ bun outdated --filter 'pkg-*' $ bun outdated --filter './' ``` -For more infomation on both these commands, see [`bun install`](https://bun.sh/docs/cli/install) and [`bun outdated`](https://bun.sh/docs/cli/outdated). +For more information on both these commands, see [`bun install`](https://bun.sh/docs/cli/install) and [`bun outdated`](https://bun.sh/docs/cli/outdated). ## Running scripts with `--filter` diff --git a/docs/install/lockfile.md b/docs/install/lockfile.md index 72e11c89442974..88b858bd5361f6 100644 --- a/docs/install/lockfile.md +++ b/docs/install/lockfile.md @@ -100,7 +100,7 @@ $ head -n3 bun.lock "workspaces": { ``` -Once `bun.lock` is generated, Bun will use it for all subsequent installs and updates through commands that read and modify the lockfile. If both lockfiles exist, `bun.lock` will be choosen over `bun.lockb`. +Once `bun.lock` is generated, Bun will use it for all subsequent installs and updates through commands that read and modify the lockfile. If both lockfiles exist, `bun.lock` will be chosen over `bun.lockb`. Bun v1.2.0 will switch the default lockfile format to `bun.lock`. diff --git a/docs/runtime/shell.md b/docs/runtime/shell.md index 4423746831636c..99983bc12f8e68 100644 --- a/docs/runtime/shell.md +++ b/docs/runtime/shell.md @@ -102,7 +102,7 @@ The default handling of non-zero exit codes can be configured by calling `.nothr import { $ } from "bun"; // shell promises will not throw, meaning you will have to // check for `exitCode` manually on every shell command. -$.nothrow(); // equivilent to $.throws(false) +$.nothrow(); // equivalent to $.throws(false) // default behavior, non-zero exit codes will throw an error $.throws(true); diff --git a/oxlint.json b/oxlint.json new file mode 100644 index 00000000000000..f596253eb7ed58 --- /dev/null +++ b/oxlint.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/refs/heads/main/npm/oxlint/configuration_schema.json", + "categories": { + "correctness": "warn" // TODO: gradually fix bugs and turn this to error + }, + "rules": { + "const-comparisons": "off", // TODO: there's a bug when comparing private identifiers. Re-enable once it's fixed. + "no-cond-assign": "error", + "no-const-assign": "error", + "no-debugger": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-empty-pattern": "error", + "import/no-duplicates": "error", + + "no-useless-escape": "off" // there's a lot of these. Should be fixed eventually. + }, + "ignorePatterns": [ + "vendor", + "build", + "test/snapshots/**", + "bench/react-hello-world/*.js", + + "test/js/node/**/parallel/**", + "test/js/node/test/fixtures", // full of JS with intentional syntax errors + "test/snippets/**", + "test/regression/issue/14477/*.tsx", + "test/js/**/*bad.js", + "test/bundler/transpiler/decorators.test.ts", // uses `arguments` as decorator + "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 + ], + "overrides": [ + { + "files": ["test/**", "examples/**", "packages/bun-internal/test/runners/**"], + "rules": { + "no-unused-vars": "off", + "no-unused-private-class-members": "off", + "no-unnecessary-await": "off" + } + }, + { + "files": ["test/**", "bench/**"], + "rules": { + "no-shadow-restricted-names": "off", + "no-empty-file": "off", + "no-unnecessary-await": "off" + } + } + ] +} diff --git a/package.json b/package.json index b0cfe5173790f7..4df6d400fe0717 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "fmt": "bun run prettier", "fmt:cpp": "bun run clang-format", "fmt:zig": "bun run zig-format", - "lint": "eslint './**/*.d.ts' --cache", - "lint:fix": "eslint './**/*.d.ts' --cache --fix", + "lint": "oxlint --config oxlint.json", + "lint:fix": "oxlint --config oxlint.json --fix", "test": "node scripts/runner.node.mjs --exec-path ./build/debug/bun-debug", "test:release": "node scripts/runner.node.mjs --exec-path ./build/release/bun", "banned": "bun packages/bun-internal-test/src/linter.ts", diff --git a/packages/bun-wasm/index.ts b/packages/bun-wasm/index.ts index 0a8fdc7e6f5870..f07be12c5017d3 100644 --- a/packages/bun-wasm/index.ts +++ b/packages/bun-wasm/index.ts @@ -66,25 +66,20 @@ const Wasi = { return Date.now(); }, environ_sizes_get() { - debugger; return 0; }, environ_get(__environ: unknown, environ_buf: unknown) { - debugger; return 0; }, fd_close(fd: number) { - debugger; return 0; }, proc_exit() {}, fd_seek(fd: number, offset_bigint: bigint, whence: unknown, newOffset: unknown) { - debugger; }, fd_write(fd: unknown, iov: unknown, iovcnt: unknown, pnum: unknown) { - debugger; }, }; diff --git a/src/bun.js/RuntimeTranspilerCache.zig b/src/bun.js/RuntimeTranspilerCache.zig index ad40bc73998cf7..3eac90cbf6aeae 100644 --- a/src/bun.js/RuntimeTranspilerCache.zig +++ b/src/bun.js/RuntimeTranspilerCache.zig @@ -9,7 +9,8 @@ /// Version 10: Constant folding for ''.charCodeAt(n) /// Version 11: Fix \uFFFF printing regression /// Version 12: "use strict"; makes it CommonJS if we otherwise don't know which one to pick. -const expected_version = 12; +/// Version 13: Hoist `import.meta.require` definition, see #15738 +const expected_version = 13; const bun = @import("root").bun; const std = @import("std"); diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index 7eb446bea22b80..77d642b3f94652 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -462,10 +462,6 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std } transpiler.runtime.allow_runtime = false; - transpiler.runtime.use_import_meta_require = switch (transpiler.transform.target orelse .browser) { - .bun, .bun_macro => true, - else => false, - }; if (try object.getTruthy(globalThis, "macro")) |macros| { macros: { diff --git a/src/bun.js/api/sockets.classes.ts b/src/bun.js/api/sockets.classes.ts index 7d30de0344487a..9b03d24a9d5729 100644 --- a/src/bun.js/api/sockets.classes.ts +++ b/src/bun.js/api/sockets.classes.ts @@ -182,9 +182,6 @@ function generate(ssl) { fn: "reload", length: 1, }, - bytesWritten: { - getter: "getBytesWritten", - }, setServername: { fn: "setServername", length: 1, diff --git a/src/bun.js/webcore/S3Stat.zig b/src/bun.js/webcore/S3Stat.zig index 56353074772bbd..53deb25bcb4c94 100644 --- a/src/bun.js/webcore/S3Stat.zig +++ b/src/bun.js/webcore/S3Stat.zig @@ -1,5 +1,5 @@ const bun = @import("../../bun.zig"); -const JSC = @import("../../JSC.zig"); +const JSC = @import("../../jsc.zig"); pub const S3Stat = struct { const log = bun.Output.scoped(.S3Stat, false); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 660cff180ddbd9..99d7e0a03a7c27 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -327,10 +327,15 @@ fn genericPathWithPrettyInitialized(path: Fs.Path, target: options.Target, top_l // TODO: outbase var buf: bun.PathBuffer = undefined; + const is_node = bun.strings.eqlComptime(path.namespace, "node"); + if (is_node and bun.strings.hasPrefixComptime(path.text, NodeFallbackModules.import_path)) { + return path; + } + // "file" namespace should use the relative file path for its display name. // the "node" namespace is also put through this code path so that the // "node:" prefix is not emitted. - if (path.isFile() or bun.strings.eqlComptime(path.namespace, "node")) { + if (path.isFile() or is_node) { const rel = bun.path.relativePlatform(top_level_dir, path.text, .loose, false); var path_clone = path; // stack-allocated temporary is not leaked because dupeAlloc on the path will @@ -3464,10 +3469,12 @@ pub const ParseTask = struct { // and then that is either replaced with the module itself, or an import to the // runtime here. const runtime_require = switch (target) { - // __require is intentionally not implemented here, as we - // always inline 'import.meta.require' and 'import.meta.require.resolve' - // Omitting it here acts as an extra assertion. - .bun, .bun_macro => "", + // Previously, Bun inlined `import.meta.require` at all usages. This broke + // code that called `fn.toString()` and parsed the code outside a module + // context. + .bun, .bun_macro => + \\export var __require = import.meta.require; + , .node => \\import { createRequire } from "node:module"; @@ -4406,10 +4413,9 @@ pub const ParseTask = struct { opts.macro_context = &this.data.macro_context; opts.package_version = task.package_version; - opts.features.auto_polyfill_require = output_format == .esm and !target.isBun(); + opts.features.auto_polyfill_require = output_format == .esm; opts.features.allow_runtime = !source.index.isRuntime(); opts.features.unwrap_commonjs_to_esm = output_format == .esm and FeatureFlags.unwrap_commonjs_to_esm; - opts.features.use_import_meta_require = target.isBun(); opts.features.top_level_await = output_format == .esm or output_format == .internal_bake_dev; opts.features.auto_import_jsx = task.jsx.parse and transpiler.options.auto_import_jsx; opts.features.trim_unused_imports = loader.isTypeScript() or (transpiler.options.trim_unused_imports orelse false); @@ -7000,18 +7006,17 @@ pub const LinkerContext = struct { try this.graph.generateSymbolImportAndUse(source_index, 0, module_ref, 1, Index.init(source_index)); // If this is a .napi addon and it's not node, we need to generate a require() call to the runtime - if (expr.data == .e_call and expr.data.e_call.target.data == .e_require_call_target and + if (expr.data == .e_call and + expr.data.e_call.target.data == .e_require_call_target and // if it's commonjs, use require() - this.options.output_format != .cjs and - // if it's esm and bun, use import.meta.require(). the code for __require is not injected into the bundle. - !this.options.target.isBun()) + this.options.output_format != .cjs) { - this.graph.generateRuntimeSymbolImportAndUse( + try this.graph.generateRuntimeSymbolImportAndUse( source_index, Index.part(1), "__require", 1, - ) catch {}; + ); } }, else => { @@ -7752,11 +7757,9 @@ pub const LinkerContext = struct { continue; } else { - // We should use "__require" instead of "require" if we're not // generating a CommonJS output file, since it won't exist otherwise. - // Disabled for target bun because `import.meta.require` will be inlined. - if (shouldCallRuntimeRequire(output_format) and !this.resolver.opts.target.isBun()) { + if (shouldCallRuntimeRequire(output_format)) { runtime_require_uses += 1; } @@ -9386,7 +9389,7 @@ pub const LinkerContext = struct { var runtime_members = &runtime_scope.members; const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("__toCommonJS").?.ref); const toESMRef = c.graph.symbols.follow(runtime_members.get("__toESM").?.ref); - const runtimeRequireRef = if (c.resolver.opts.target.isBun()) null else c.graph.symbols.follow(runtime_members.get("__require").?.ref); + const runtimeRequireRef = if (c.options.output_format == .cjs) null else c.graph.symbols.follow(runtime_members.get("__require").?.ref); const result = c.generateCodeForFileInChunkJS( &buffer_writer, @@ -9855,10 +9858,11 @@ pub const LinkerContext = struct { var runtime_members = &runtime_scope.members; const toCommonJSRef = c.graph.symbols.follow(runtime_members.get("__toCommonJS").?.ref); const toESMRef = c.graph.symbols.follow(runtime_members.get("__toESM").?.ref); - const runtimeRequireRef = if (c.resolver.opts.target.isBun() or c.options.output_format == .cjs) null else c.graph.symbols.follow(runtime_members.get("__require").?.ref); + const runtimeRequireRef = if (c.options.output_format == .cjs) null else c.graph.symbols.follow(runtime_members.get("__require").?.ref); { const print_options = js_printer.Options{ + .bundling = true, .indent = .{}, .has_run_symbol_renamer = true, @@ -10015,7 +10019,7 @@ pub const LinkerContext = struct { switch (c.options.output_format) { .internal_bake_dev => { - const start = bun.bake.getHmrRuntime(if (c.options.target.isBun()) .server else .client); + const start = bun.bake.getHmrRuntime(if (c.options.target.isServerSide()) .server else .client); j.pushStatic(start); line_offset.advance(start); }, @@ -12513,6 +12517,7 @@ pub const LinkerContext = struct { }; const print_options = js_printer.Options{ + .bundling = true, // TODO: IIFE .indent = .{}, .commonjs_named_exports = ast.commonjs_named_exports, @@ -14099,7 +14104,7 @@ pub const LinkerContext = struct { }, ) catch unreachable; } - } else if (c.resolver.opts.target == .browser and JSC.HardcodedModule.Aliases.has(next_source.path.pretty, .browser)) { + } else if (c.resolver.opts.target == .browser and bun.strings.hasPrefixComptime(next_source.path.text, NodeFallbackModules.import_path)) { c.log.addRangeErrorFmtWithNote( source, r, diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index 565a056a60de3f..bd54b95070707b 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -112,7 +112,6 @@ export default { DatePrototypeToString: uncurryThis(Date.prototype.toString), ErrorCaptureStackTrace, ErrorPrototypeToString: uncurryThis(Error.prototype.toString), - FunctionPrototypeToString: uncurryThis(Function.prototype.toString), JSONStringify: JSON.stringify, MapPrototypeGetSize: getGetter(Map, "size"), MapPrototypeEntries: uncurryThis(Map.prototype.entries), diff --git a/src/js/node/http2.ts b/src/js/node/http2.ts index a48c0027bcfa7c..f480b4212bf40f 100644 --- a/src/js/node/http2.ts +++ b/src/js/node/http2.ts @@ -2419,7 +2419,7 @@ class ServerHttp2Session extends Http2Session { // throwNotImplemented("ServerHttp2Stream.prototype.origin()"); } - constructor(socket: TLSSocket | Socket, options?: Http2ConnectOptions, server: Http2Server) { + constructor(socket: TLSSocket | Socket, options?: Http2ConnectOptions, server?: Http2Server) { super(); this[kServer] = server; this.#connected = true; diff --git a/src/js/node/net.ts b/src/js/node/net.ts index 1fad0650748a0a..2e7e28e7e1f260 100644 --- a/src/js/node/net.ts +++ b/src/js/node/net.ts @@ -1342,7 +1342,6 @@ class Server extends EventEmitter { }); } else { this._handle = Bun.listen({ - exclusive, port, hostname, tls, diff --git a/src/js_ast.zig b/src/js_ast.zig index fe7456598e30ba..4263c9a60c8517 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -6917,8 +6917,6 @@ pub const Ast = struct { wrapper_ref: Ref = Ref.None, require_ref: Ref = Ref.None, - prepend_part: ?Part = null, - // These are used when bundling. They are filled in during the parser pass // since we already have to traverse the AST then anyway and the parser pass // is conveniently fully parallelized. diff --git a/src/js_parser.zig b/src/js_parser.zig index 9d3803f7519ea9..62e6515a8eb5d0 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -18863,22 +18863,20 @@ fn NewParser_( } }, .e_import_meta => { - // Make `import.meta.url` side effect free. - if (strings.eqlComptime(name, "url")) { - return p.newExpr( - E.Dot{ - .target = target, - .name = name, - .name_loc = name_loc, - .can_be_removed_if_unused = true, - }, - target.loc, - ); - } - if (strings.eqlComptime(name, "main")) { return p.valueForImportMetaMain(false, target.loc); } + + // Make all property accesses on `import.meta.url` side effect free. + return p.newExpr( + E.Dot{ + .target = target, + .name = name, + .name_loc = name_loc, + .can_be_removed_if_unused = true, + }, + target.loc, + ); }, .e_require_call_target => { if (strings.eqlComptime(name, "main")) { @@ -23749,8 +23747,11 @@ fn NewParser_( .force_cjs_to_esm = p.unwrap_all_requires or exports_kind == .esm_with_dynamic_fallback_from_cjs, .uses_module_ref = p.symbols.items[p.module_ref.inner_index].use_count_estimate > 0, .uses_exports_ref = p.symbols.items[p.exports_ref.inner_index].use_count_estimate > 0, - .uses_require_ref = p.runtime_imports.__require != null and - p.symbols.items[p.runtime_imports.__require.?.inner_index].use_count_estimate > 0, + .uses_require_ref = if (p.options.bundle) + p.runtime_imports.__require != null and + p.symbols.items[p.runtime_imports.__require.?.inner_index].use_count_estimate > 0 + else + p.symbols.items[p.require_ref.inner_index].use_count_estimate > 0, .commonjs_module_exports_assigned_deoptimized = p.commonjs_module_exports_assigned_deoptimized, .top_level_await_keyword = p.top_level_await_keyword, .commonjs_named_exports = p.commonjs_named_exports, diff --git a/src/js_printer.zig b/src/js_printer.zig index 3116a92532ef40..fe3e6d4cd44909 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -434,6 +434,7 @@ pub const SourceMapHandler = struct { }; pub const Options = struct { + bundling: bool = false, transform_imports: bool = true, to_commonjs_ref: Ref = Ref.None, to_esm_ref: Ref = Ref.None, @@ -475,22 +476,6 @@ pub const Options = struct { // const_values: Ast.ConstValuesMap = .{}, ts_enums: Ast.TsEnumsMap = .{}, - // TODO: remove this - // The reason for this is: - // 1. You're bundling a React component - // 2. jsx auto imports are prepended to the list of parts - // 3. The AST modification for bundling only applies to the final part - // 4. This means that it will try to add a toplevel part which is not wrapped in the arrow function, which is an error - // TypeError: $30851277 is not a function. (In '$30851277()', '$30851277' is undefined) - // at (anonymous) (0/node_modules.server.e1b5ffcd183e9551.jsb:1463:21) - // at #init_react/jsx-dev-runtime.js (0/node_modules.server.e1b5ffcd183e9551.jsb:1309:8) - // at (esm) (0/node_modules.server.e1b5ffcd183e9551.jsb:1480:30) - - // The temporary fix here is to tag a stmts ptr as the one we want to prepend to - // Then, when we're JUST about to print it, we print the body of prepend_part_value first - prepend_part_key: ?*anyopaque = null, - prepend_part_value: ?*js_ast.Part = null, - // If we're writing out a source map, this table of line start indices lets // us do binary search on to figure out what line a given AST node came from line_offset_tables: ?SourceMap.LineOffsetTable.List = null, @@ -1836,9 +1821,7 @@ fn NewPrinter( p.print("("); } - if (module_type == .esm and is_bun_platform) { - p.print("import.meta.require"); - } else if (p.options.require_ref) |ref| { + if (p.options.require_ref) |ref| { p.printSymbol(ref); } else { p.print("require"); @@ -2072,7 +2055,9 @@ fn NewPrinter( // // This is currently only used in Bun's runtime for CommonJS modules // referencing import.meta - if (comptime Environment.allow_assert) + // + // TODO: This assertion trips when using `import.meta` with `--format=cjs` + if (comptime Environment.isDebug) bun.assert(p.options.module_type == .cjs); p.printSymbol(p.options.import_meta_ref); @@ -2277,9 +2262,7 @@ fn NewPrinter( p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); - if (p.options.module_type == .esm and is_bun_platform) { - p.print("import.meta.require.main"); - } else if (p.options.require_ref) |require_ref| { + if (p.options.require_ref) |require_ref| { p.printSymbol(require_ref); p.print(".main"); } else { @@ -2290,9 +2273,7 @@ fn NewPrinter( p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); - if (p.options.module_type == .esm and is_bun_platform) { - p.print("import.meta.require"); - } else if (p.options.require_ref) |require_ref| { + if (p.options.require_ref) |require_ref| { p.printSymbol(require_ref); } else { p.print("require"); @@ -2302,9 +2283,7 @@ fn NewPrinter( p.printSpaceBeforeIdentifier(); p.addSourceMapping(expr.loc); - if (p.options.module_type == .esm and is_bun_platform) { - p.print("import.meta.require.resolve"); - } else if (p.options.require_ref) |require_ref| { + if (p.options.require_ref) |require_ref| { p.printSymbol(require_ref); p.print(".resolve"); } else { @@ -2331,9 +2310,7 @@ fn NewPrinter( p.printSpaceBeforeIdentifier(); - if (p.options.module_type == .esm and is_bun_platform) { - p.print("import.meta.require.resolve"); - } else if (p.options.require_ref) |require_ref| { + if (p.options.require_ref) |require_ref| { p.printSymbol(require_ref); p.print(".resolve"); } else { @@ -2550,15 +2527,6 @@ fn NewPrinter( p.printWhitespacer(ws(" => ")); var wasPrinted = false; - - // This is more efficient than creating a new Part just for the JSX auto imports when bundling - if (comptime rewrite_esm_to_cjs) { - if (@intFromPtr(p.options.prepend_part_key) > 0 and @intFromPtr(e.body.stmts.ptr) == @intFromPtr(p.options.prepend_part_key)) { - p.printTwoBlocksInOne(e.body.loc, e.body.stmts, p.options.prepend_part_value.?.stmts); - wasPrinted = true; - } - } - if (e.body.stmts.len == 1 and e.prefer_expr) { switch (e.body.stmts[0].data) { .s_return => { @@ -5803,14 +5771,24 @@ pub fn printAst( defer { imported_module_ids_list = printer.imported_module_ids; } - if (tree.prepend_part) |part| { - for (part.stmts) |stmt| { - try printer.printStmt(stmt); - if (printer.writer.getError()) {} else |err| { - return err; - } - printer.printSemicolonIfNeeded(); - } + + if (!opts.bundling and + tree.uses_require_ref and + tree.exports_kind == .esm and + opts.target == .bun) + { + // Hoist the `var {require}=import.meta;` declaration. Previously, + // `import.meta.require` was inlined into transpiled files, which + // meant calling `func.toString()` on a function with `require` + // would observe `import.meta.require` inside of the source code. + // Normally, Bun doesn't guarantee `Function.prototype.toString` + // will match the untranspiled source code, but in this case the new + // code is not valid outside of an ES module (eg, in `new Function`) + // https://github.com/oven-sh/bun/issues/15738#issuecomment-2574283514 + // + // This is never a symbol collision because `uses_require_ref` means + // `require` must be an unbound variable. + printer.print("var {require}=import.meta;"); } for (tree.parts.slice()) |part| { @@ -6089,15 +6067,6 @@ pub fn printCommonJS( imported_module_ids_list = printer.imported_module_ids; } - if (tree.prepend_part) |part| { - for (part.stmts) |stmt| { - try printer.printStmt(stmt); - if (printer.writer.getError()) {} else |err| { - return err; - } - printer.printSemicolonIfNeeded(); - } - } for (tree.parts.slice()) |part| { for (part.stmts) |stmt| { try printer.printStmt(stmt); diff --git a/src/runtime.zig b/src/runtime.zig index c7309a280e9474..3585fe11b04fed 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -191,10 +191,6 @@ pub const Runtime = struct { trim_unused_imports: bool = false, - /// Use `import.meta.require()` instead of require()? - /// This is only supported with --target=bun - use_import_meta_require: bool = false, - /// Allow runtime usage of require(), converting `require` into `__require` auto_polyfill_require: bool = false, @@ -240,7 +236,6 @@ pub const Runtime = struct { .dead_code_elimination, .set_breakpoint_on_first_line, .trim_unused_imports, - .use_import_meta_require, .dont_bundle_twice, .commonjs_at_runtime, .emit_decorator_metadata, diff --git a/src/transpiler.zig b/src/transpiler.zig index b0078da1938f88..ead4afecc53187 100644 --- a/src/transpiler.zig +++ b/src/transpiler.zig @@ -1083,6 +1083,7 @@ pub const Transpiler = struct { source, false, .{ + .bundling = false, .runtime_imports = ast.runtime_imports, .require_ref = ast.require_ref, .css_import_behavior = transpiler.options.cssImportBehavior(), @@ -1105,6 +1106,7 @@ pub const Transpiler = struct { source, false, .{ + .bundling = false, .runtime_imports = ast.runtime_imports, .require_ref = ast.require_ref, .source_map_handler = source_map_context, @@ -1128,6 +1130,7 @@ pub const Transpiler = struct { source, is_bun, .{ + .bundling = false, .runtime_imports = ast.runtime_imports, .require_ref = ast.require_ref, .css_import_behavior = transpiler.options.cssImportBehavior(), @@ -1362,7 +1365,6 @@ pub const Transpiler = struct { opts.features.allow_runtime = transpiler.options.allow_runtime; opts.features.set_breakpoint_on_first_line = this_parse.set_breakpoint_on_first_line; opts.features.trim_unused_imports = transpiler.options.trim_unused_imports orelse loader.isTypeScript(); - opts.features.use_import_meta_require = target.isBun(); opts.features.no_macros = transpiler.options.no_macros; opts.features.runtime_transpiler_cache = this_parse.runtime_transpiler_cache; opts.transform_only = transpiler.options.transform_only; diff --git a/test/bundler/bundler_html.test.ts b/test/bundler/bundler_html.test.ts index 6314971925e8f0..1a2d57334e5d9e 100644 --- a/test/bundler/bundler_html.test.ts +++ b/test/bundler/bundler_html.test.ts @@ -813,7 +813,6 @@ body { // Test that sourcemap comments are not included in HTML and CSS files itBundled("html/no-sourcemap-comments", { outdir: "out/", - sourceMap: "linked", files: { "/index.html": ` diff --git a/test/bundler/esbuild/default.test.ts b/test/bundler/esbuild/default.test.ts index 39847ec22af728..785a5eca752696 100644 --- a/test/bundler/esbuild/default.test.ts +++ b/test/bundler/esbuild/default.test.ts @@ -1,4 +1,5 @@ import assert from "assert"; +import path from "path"; import { describe, expect } from "bun:test"; import { osSlashes } from "harness"; import { dedent, ESBUILD_PATH, itBundled } from "../expectBundled"; @@ -2797,20 +2798,25 @@ describe("bundler", () => { }, bundling: false, }); - itBundled("default/ImportMetaCommonJS", { + itBundled("default/ImportMetaCommonJS", ({ root }) => ({ + // Currently Bun emits `import.meta` instead of correctly + // polyfilling its properties. + todo: true, files: { "/entry.js": ` - import fs from "fs"; - import { fileURLToPath } from "url"; - console.log(fs.existsSync(fileURLToPath(import.meta.url)), fs.existsSync(import.meta.path)); + import fs from "fs"; + import { fileURLToPath } from "url"; + console.log(fileURLToPath(import.meta.url) === ${JSON.stringify(path.join(root, "out.cjs"))}); `, }, + outfile: "out.cjs", format: "cjs", target: "node", run: { + runtime: "node", stdout: "true true", }, - }); + })); itBundled("default/ImportMetaES6", { files: { "/entry.js": `console.log(import.meta.url, import.meta.path)`, diff --git a/test/bundler/esbuild/hello.ts b/test/bundler/esbuild/hello.ts new file mode 100644 index 00000000000000..483b7660f686de --- /dev/null +++ b/test/bundler/esbuild/hello.ts @@ -0,0 +1,3 @@ +import fs from "fs"; +import { fileURLToPath } from "url"; +console.log(fs.existsSync(fileURLToPath(import.meta.url)), fs.existsSync(import.meta.path)); diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index 36c52021f3397a..f229a603670b3f 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -1589,6 +1589,11 @@ for (const [key, blob] of build.outputs) { // no idea why this logs. ¯\_(ツ)_/¯ result = result.replace(/\[Event_?Loop\] enqueueTaskConcurrent\(RuntimeTranspilerStore\)\n/gi, ""); + // when the inspector runs (can be due to VSCode extension), there is + // a bug that in debug modes the console logs extra stuff + if (name === "stderr" && process.env.BUN_INSPECT_CONNECT_TO) { + result = result.replace(/(?:^|\n)\/[^\n]*: CONSOLE LOG[^\n]*(\n|$)/g, "$1").trim(); + } if (typeof expected === "string") { expected = dedent(expected).trim(); diff --git a/test/bundler/native-plugin.test.ts b/test/bundler/native-plugin.test.ts index 8a09905eaf7601..942461228f6896 100644 --- a/test/bundler/native-plugin.test.ts +++ b/test/bundler/native-plugin.test.ts @@ -74,6 +74,11 @@ values;`, await Bun.$`${bunExe()} i && ${bunExe()} build:napi`.env(bunEnv).cwd(tempdir); }); + beforeEach(() => { + const tempdir2 = tempDirWithFiles("native-plugins", {}); + process.chdir(tempdir2); + }); + afterEach(async () => { await Bun.$`rm -rf ${outdir}`; process.chdir(cwd); @@ -672,6 +677,7 @@ console.log(JSON.stringify(json)) }, }, ], + throw: true, }); expect(result.success).toBeTrue(); diff --git a/test/bundler/transpiler/function-tostring-require.test.ts b/test/bundler/transpiler/function-tostring-require.test.ts new file mode 100644 index 00000000000000..2ea355f1a3ca51 --- /dev/null +++ b/test/bundler/transpiler/function-tostring-require.test.ts @@ -0,0 +1,13 @@ +import { test, expect } from "bun:test"; + +test("toString doesnt observe import.meta.require", () => { + function hello() { + return typeof require("fs") === "string" ? "from eval" : "main function"; + } + const newFunctionBody = `return ${hello.toString()}`; + const loadFakeModule = new Function("require", newFunctionBody)(id => `fake require ${id}`); + expect(hello()).toBe("main function"); + expect(loadFakeModule()).toBe("from eval"); +}); + +export {}; diff --git a/test/js/bun/css/css.test.ts b/test/js/bun/css/css.test.ts index 2e4405893a4234..5c85ae7b07d998 100644 --- a/test/js/bun/css/css.test.ts +++ b/test/js/bun/css/css.test.ts @@ -2,9 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { describe, expect, test } from "bun:test"; +import { describe, test } from "bun:test"; import "harness"; -import path from "path"; import { attrTest, cssTest, indoc, minify_test, minifyTest, prefix_test } from "./util"; describe("css tests", () => { diff --git a/test/js/bun/ffi/ffi.test.js b/test/js/bun/ffi/ffi.test.js index 39782240bfadb6..93f1abbfc19dd9 100644 --- a/test/js/bun/ffi/ffi.test.js +++ b/test/js/bun/ffi/ffi.test.js @@ -927,14 +927,6 @@ const libSymbols = { returns: "int", args: ["ptr", "ptr", "usize"], }, - pthread_attr_getguardsize: { - returns: "int", - args: ["ptr", "ptr"], - }, - pthread_attr_setguardsize: { - returns: "int", - args: ["ptr", "usize"], - }, login_tty: { returns: "int", args: ["int"], diff --git a/test/js/bun/resolve/resolve.test.ts b/test/js/bun/resolve/resolve.test.ts index d2fa6fbb97f561..969449ffd01e82 100644 --- a/test/js/bun/resolve/resolve.test.ts +++ b/test/js/bun/resolve/resolve.test.ts @@ -1,12 +1,8 @@ import { it, expect } from "bun:test"; import { mkdirSync, writeFileSync } from "fs"; -import { join } from "path"; +import { join, sep } from "path"; import { bunExe, bunEnv, tempDirWithFiles, isWindows } from "harness"; import { pathToFileURL } from "bun"; -import { expect, it } from "bun:test"; -import { mkdirSync, writeFileSync } from "fs"; -import { bunEnv, bunExe, tempDirWithFiles } from "harness"; -import { join, sep } from "path"; it("spawn test file", () => { writePackageJSONImportsFixture(); diff --git a/test/js/web/fetch/fetch-gzip.test.ts b/test/js/web/fetch/fetch-gzip.test.ts index 83028ae12bac47..8f162b004a8f00 100644 --- a/test/js/web/fetch/fetch-gzip.test.ts +++ b/test/js/web/fetch/fetch-gzip.test.ts @@ -210,8 +210,7 @@ it("fetch() with a gzip response works (multiple chunks, TCP server)", async don await write("\r\n"); socket.flush(); - }, - drain(socket) {}, + } }, }); await 1; diff --git a/test/snippets/react-context-value-func.tsx b/test/snippets/react-context-value-func.tsx index 800ad428d7c134..c693717466c499 100644 --- a/test/snippets/react-context-value-func.tsx +++ b/test/snippets/react-context-value-func.tsx @@ -10,7 +10,7 @@ const ContextProvider = ({ children }) => { return {children(foo)}; }; -const ContextValue = ({}) => ( +const ContextValue = () => ( {foo => { if (foo) {