cli: add --check flag to syntax-check a file without executing#30981
cli: add --check flag to syntax-check a file without executing#30981robobun wants to merge 17 commits into
--check flag to syntax-check a file without executing#30981Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a ChangesSyntax check flag implementation
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/runtime/cli/Arguments.rs`:
- Around line 1134-1141: The conflict check incorrectly tests
!ctx.runtime_options.eval.script.is_empty() which misses cases like --eval "" or
--print ""; update the condition that follows ctx.runtime_options.syntax_check
to detect flag presence instead of script content by checking
args.flag(b"--eval") || args.flag(b"--print") (or any equivalent runtime
presence indicator), and keep the existing error path (Output::err_generic and
Global::exit(9)) so that any use of --check together with either --eval or
--print triggers the conflict error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 12987e53-0833-49e6-a0c4-23f1647a1ae1
📒 Files selected for processing (7)
completions/bun.zshsrc/jsc/bindings/wtf-bindings.cppsrc/options_types/context.rssrc/runtime/cli/Arguments.rssrc/runtime/cli/mod.rssrc/runtime/cli/run_command.rstest/cli/run/check.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/cli/run/check.test.ts`:
- Around line 161-174: Refactor the test "rejects combining --check with --eval
or --print" to use Jest's parameterized API (test.each) instead of a for-loop:
create a test.each([...]) that enumerates the flag variants
["-e","--eval","-p","--print"] and for each case spawn Bun with cmd [bunExe(),
"--check", flag, "1 + 1"], capture stdout/stderr/exit and assert the same three
expectations (stderr contains "either --check or --eval", stdout is empty,
exitCode !== 0); keep the existing Bun.spawn usage and variable names (proc,
bunExe, bunEnv) and preserve async handling so failures report per-case in test
output.
- Around line 58-61: The test currently asserts exitCode before guarding stderr
and verifying filesystem state; move the filesystem assertion using
Bun.file(`${dir}/ran.txt`).exists() to run before the exitCode assertion, insert
the guard pattern if (exitCode !== 0) { expect(stderr).toBe(""); } immediately
before asserting exitCode, and finally assert expect(exitCode).toBe(0);—keep the
existing stdout and stderr assertions (and the new guard) above the final
exitCode check so exitCode is asserted last.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 19364783-2846-4b8a-8206-2100edd0d18f
📒 Files selected for processing (2)
completions/bun.zshtest/cli/run/check.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/cli/run/check.test.ts`:
- Around line 161-172: The test in test.each([...]) currently asserts any
non-zero exit code for the conflicting "--check" + eval/print flags; change the
final assertion from expect(exitCode).not.toBe(0) to expect(exitCode).toBe(9) so
the test verifies the exact contract for the conflict exit code; update the
assertion in the async test body that awaits proc.exited (the exitCode variable)
to assert equality with 9 while leaving the existing stdout/stderr checks and
Bun.spawn/using setup intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: eeb6082a-974e-4c59-9310-04be509dc2be
📒 Files selected for processing (1)
test/cli/run/check.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/cli/run/check.test.ts`:
- Line 20: The test currently asserts raw stderr is empty via
expect(stderr).toBe(""), which is flaky because ASAN can emit a one-time startup
warning; before the assertion, normalize stderr by splitting into lines and
filtering out any lines that start with "WARNING: ASAN interferes" (e.g., use
stderr.split(...).filter(line => !line.startsWith("WARNING: ASAN
interferes")).join(...)), then assert the filtered stderr is empty; apply the
same change for the other assertions in this file that use
expect(stderr).toBe("") (the occurrences around the other checks noted).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ae402ef5-cabf-425c-b006-5bd31e4c885f
📒 Files selected for processing (1)
test/cli/run/check.test.ts
There was a problem hiding this comment.
All my earlier nits have been addressed or reasonably deferred and I didn't find anything new, but since this adds a new user-facing CLI flag wired into the core bun/bun run/node-mode dispatch (plus a drive-by wtf-bindings.cpp fix), it's worth a maintainer sign-off.
Extended reasoning...
Overview
Adds bun --check <file> (Node's --check/-c equivalent): a new syntax_check field on RuntimeOptions, a --check entry in RUNTIME_PARAMS_, an ~80-line RunCommand::exec_check that reads the file/stdin and runs bun_js_parser::Parser directly without booting the VM, early-exit dispatch hooks in exec_auto_or_run and exec_as_if_node, a 200-line test suite, a zsh completion entry, and an unrelated one-line assert → ASSERT fix in wtf-bindings.cpp.
Security risks
None identified. The new path only reads a user-specified file (or stdin) and parses it; it never executes code, resolves imports, or touches the network. The unsafe { ctx.log() } blocks follow the existing documented pattern for the process-lifetime CLI log.
Level of scrutiny
Medium. This is a new user-facing feature threaded through the core CLI dispatch chain — an area where ordering and positional-handling edge cases are subtle (this PR itself went through five follow-up commits during review to fix -e "" detection, - → stdin, the run-positional strip under node-argv0, and --parallel/--filter precedence). The implementation now looks correct and is well-tested, but it is not a mechanical change.
Other factors
- All prior inline feedback (mine and CodeRabbit's) is resolved; the two remaining items (no extension resolution for
bun --check foo→foo.js;bun test --checkaccepted-but-ignored) were explicitly deferred by the author with reasonable justification, and a maintainer may want to confirm those deferrals. - The bundled
wtf-bindings.cppchange is trivially correct but is an unrelated build-fix riding along. - Good test coverage (12 cases including no-execute guarantee, stdin,
-, TS/ESM/TLA, missing file,--evalconflict with exit 9,bun run --check). - No CODEOWNERS match for the touched paths.
StatusReady for maintainer review. Feature complete, rebased onto current main, 14/14 tests pass locally, all 13 review threads resolved, docs entry added. CILatest head (11203f8, adds --if-present support per review): Build #60836 shows 44+ pass and the same single failing lane: That same lane fails identically on main's tip (a1dd647d5b, the exact commit this branch is rebased onto), so it's pre-existing main breakage, not this diff. No failure in any run has touched Earlier flakes across runs, for the record: a v8-heap-snapshot SIGKILL (OOM-prone test, unrelated), an expired macOS runner, and two Deferred nits (for maintainer to confirm)
|
Equivalent to `node --check` / `node -c`: parses the entry point (or stdin if none given) with Bun's JS/TS parser and exits 0 if valid, or prints the syntax error and exits 1. Never boots the JS VM. `-c` is already taken by `--config` in Bun, so only the long `--check` spelling is provided. Combining `--check` with `--eval`/`--print` is rejected, matching Node's behavior. Also fixes a unified-build include-order break in wtf-bindings.cpp where a bare `assert()` was used without including <cassert>; switched to WTF's `ASSERT()` to match the rest of the file.
Detect the conflict by flag presence (args.option().is_some()) instead of checking whether the eval script is non-empty, so `bun --check -e ''` is rejected the same way Node rejects `node --check --eval ''`.
exec_check is called from both exec_auto_or_run (where positionals[0] may be the 'run' subcommand) and exec_as_if_node (where positionals[0] is always the script name). Only strip the leading 'run' in the former so bun-as-node `node --check run` checks a file named 'run' instead of reading stdin.
Otherwise `bun --check --filter='*' foo` routes into filter_run and executes the script, dropping the syntax-only guarantee. --check now takes precedence over multi-run/filter dispatch.
2ab1374 to
bfbba29
Compare
The not.toContain assertion was vacuous after expect(stdout).toBe("") -
reordered so it can fail independently with a clearer message.
bun run --if-present missing.js exits 0 via exec_with_cfg, but the --check dispatch bypassed that path and always exited 1 on ENOENT. Exit 0 on ENOENT when --if-present is set, matching the documented "Exit without an error if the entrypoint does not exist" semantics.
read_from pread()s from offset 0 which fails with ESPIPE on non-seekable fds, breaking process substitution and FIFOs (bun --check <(gen-js)). Open + stream-read instead, matching the stdin branch and node --check.
exec_check only consulted DEFAULT_LOADERS, so -l .js:ts had no effect on the syntax dialect. Check ctx.args.loaders first, matching the run path's precedence.
'Cannot find module' was wrong for EISDIR/EACCES where the path was found but unreadable. ENOENT keeps the familiar wording.
What
Adds
bun --check <file>, equivalent to Node'snode --check/node -c: parse the entry point for syntax errors and exit without ever executing it.Behavior
bun --check <file>— parses<file>; exits 0 silently on success, prints the syntax error and exits 1 on failure.bun --checkwith no file — reads from stdin (likenode --check).bun run --check <file>— same.awaitand ESMimport/exportare accepted.--checkcombined with--eval/--printis rejected witheither --check or --eval can be used, not bothand exit code 9, matching Node.The short
-cspelling is not provided because-cis already--configin Bun.How
syntax_check: boolonRuntimeOptions, populated from a--checkentry inRUNTIME_PARAMS_.RunCommand::exec_check()reads the file (or stdin via a streamingread()loop —preadfails on pipes), picks a loader from the extension, and runsbun_js_parser::Parserdirectly against an emptyDefine. If the parse errors or the log has errors, it prints them and exits 1; otherwise exits 0.exec_auto_or_run(forbun/bun run) andexec_as_if_node(forbun-as-node) before VM boot.Rebased onto main after #31783 and related refactors; the only conflict was a doc comment in
run_command.rs. An earlierwtf-bindings.cppassert fix was dropped from this branch because main fixed it independently.Tests
test/cli/run/check.test.tscovers: valid/invalid files, no-execute guarantee, TS syntax, ESM syntax, top-level await, stdin, missing file,--evalconflict, andbun run --check.