mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 02:18:47 +00:00
Compare commits
1 Commits
zack/ssg-3
...
claude/nod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
715413adc0 |
@@ -303,34 +303,9 @@ function getCppAgent(platform, options) {
|
||||
}
|
||||
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {string}
|
||||
*/
|
||||
function getLinkBunAgent(platform, options) {
|
||||
const { os, arch, distro } = platform;
|
||||
|
||||
if (os === "darwin") {
|
||||
return {
|
||||
queue: `build-${os}`,
|
||||
os,
|
||||
arch,
|
||||
};
|
||||
}
|
||||
|
||||
if (os === "windows") {
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "r8g.large" : "r7i.large",
|
||||
});
|
||||
}
|
||||
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "r8g.xlarge" : "r7i.xlarge",
|
||||
instanceType: arch === "aarch64" ? "c8g.16xlarge" : "c7i.16xlarge",
|
||||
cpuCount: 32,
|
||||
threadsPerCore: 1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -381,7 +356,7 @@ function getTestAgent(platform, options) {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: delete this block when we upgrade to mimalloc v3
|
||||
// TODO: `dev-server-ssr-110.test.ts` and `next-build.test.ts` run out of memory at 8GB of memory, so use 16GB instead.
|
||||
if (os === "windows") {
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: "c7i.2xlarge",
|
||||
@@ -527,7 +502,7 @@ function getLinkBunStep(platform, options) {
|
||||
key: `${getTargetKey(platform)}-build-bun`,
|
||||
label: `${getTargetLabel(platform)} - build-bun`,
|
||||
depends_on: [`${getTargetKey(platform)}-build-cpp`, `${getTargetKey(platform)}-build-zig`],
|
||||
agents: getLinkBunAgent(platform, options),
|
||||
agents: getCppAgent(platform, options),
|
||||
retry: getRetry(),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
env: {
|
||||
|
||||
103
.github/workflows/CLAUDE.md
vendored
103
.github/workflows/CLAUDE.md
vendored
@@ -1,103 +0,0 @@
|
||||
# GitHub Actions Workflow Maintenance Guide
|
||||
|
||||
This document provides guidance for maintaining the GitHub Actions workflows in this repository.
|
||||
|
||||
## format.yml Workflow
|
||||
|
||||
### Overview
|
||||
The `format.yml` workflow runs code formatters (Prettier, clang-format, and Zig fmt) on pull requests and pushes to main. It's optimized for speed by running all formatters in parallel.
|
||||
|
||||
### Key Components
|
||||
|
||||
#### 1. Clang-format Script (`scripts/run-clang-format.sh`)
|
||||
- **Purpose**: Formats C++ source and header files
|
||||
- **What it does**:
|
||||
- Reads C++ files from `cmake/sources/CxxSources.txt`
|
||||
- Finds all header files in `src/` and `packages/`
|
||||
- Excludes third-party directories (libuv, napi, deps, vendor, sqlite, etc.)
|
||||
- Requires specific clang-format version (no fallbacks)
|
||||
|
||||
**Important exclusions**:
|
||||
- `src/napi/` - Node API headers (third-party)
|
||||
- `src/bun.js/bindings/libuv/` - libuv headers (third-party)
|
||||
- `src/bun.js/bindings/sqlite/` - SQLite headers (third-party)
|
||||
- `src/bun.js/api/ffi-*.h` - FFI headers (generated/third-party)
|
||||
- `src/deps/` - Dependencies (third-party)
|
||||
- Files in `vendor/`, `third_party/`, `generated/` directories
|
||||
|
||||
#### 2. Parallel Execution
|
||||
The workflow runs all three formatters simultaneously:
|
||||
- Each formatter outputs with a prefix (`[prettier]`, `[clang-format]`, `[zig]`)
|
||||
- Output is streamed in real-time without blocking
|
||||
- Uses GitHub Actions groups (`::group::`) for collapsible sections
|
||||
|
||||
#### 3. Tool Installation
|
||||
|
||||
##### Clang-format-19
|
||||
- Installs ONLY `clang-format-19` package (not the entire LLVM toolchain)
|
||||
- Uses `--no-install-recommends --no-install-suggests` to skip unnecessary packages
|
||||
- Quiet installation with `-qq` and `-o=Dpkg::Use-Pty=0`
|
||||
|
||||
##### Zig
|
||||
- Downloads from `oven-sh/zig` releases (musl build for static linking)
|
||||
- URL: `https://github.com/oven-sh/zig/releases/download/autobuild-{COMMIT}/bootstrap-x86_64-linux-musl.zip`
|
||||
- Extracts to temp directory to avoid polluting the repository
|
||||
- Directory structure: `bootstrap-x86_64-linux-musl/zig`
|
||||
|
||||
### Updating the Workflow
|
||||
|
||||
#### To update Zig version:
|
||||
1. Find the new commit hash from https://github.com/oven-sh/zig/releases
|
||||
2. Replace the hash in the wget URL (line 65 of format.yml)
|
||||
3. Test that the URL is valid and the binary works
|
||||
|
||||
#### To update clang-format version:
|
||||
1. Update `LLVM_VERSION_MAJOR` environment variable at the top of format.yml
|
||||
2. Update the version check in `scripts/run-clang-format.sh`
|
||||
|
||||
#### To add/remove file exclusions:
|
||||
1. Edit the exclusion patterns in `scripts/run-clang-format.sh` (lines 34-39)
|
||||
2. Test locally to ensure the right files are being formatted
|
||||
|
||||
### Performance Optimizations
|
||||
1. **Parallel execution**: All formatters run simultaneously
|
||||
2. **Minimal installations**: Only required packages, no extras
|
||||
3. **Temp directories**: Tools downloaded to temp dirs, cleaned up after use
|
||||
4. **Streaming output**: Real-time feedback without buffering
|
||||
5. **Early start**: Formatting begins immediately after each tool is ready
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**If formatters appear to run sequentially:**
|
||||
- Check if output is being buffered (should use `sed` for line prefixing)
|
||||
- Ensure background processes use `&` and proper wait commands
|
||||
|
||||
**If third-party files are being formatted:**
|
||||
- Review exclusion patterns in `scripts/run-clang-format.sh`
|
||||
- Check if new third-party directories were added that need exclusion
|
||||
|
||||
**If clang-format installation is slow:**
|
||||
- Ensure using minimal package installation flags
|
||||
- Check if apt cache needs updating
|
||||
- Consider caching the clang-format binary between runs
|
||||
|
||||
### Testing Changes Locally
|
||||
|
||||
```bash
|
||||
# Test the clang-format script
|
||||
export LLVM_VERSION_MAJOR=19
|
||||
./scripts/run-clang-format.sh format
|
||||
|
||||
# Test with check mode (no modifications)
|
||||
./scripts/run-clang-format.sh check
|
||||
|
||||
# Test specific file exclusions
|
||||
./scripts/run-clang-format.sh format 2>&1 | grep -E "(libuv|napi|deps)"
|
||||
# Should return nothing if exclusions work correctly
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
- The script defaults to **format** mode (modifies files)
|
||||
- Always test locally before pushing workflow changes
|
||||
- The musl Zig build works on glibc systems due to static linking
|
||||
- Keep the exclusion list updated as new third-party code is added
|
||||
24
.github/workflows/auto-label-claude-prs.yml
vendored
24
.github/workflows/auto-label-claude-prs.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Auto-label Claude PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
if: github.event.pull_request.user.login == 'robobun'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Add claude label to PRs from robobun
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['claude']
|
||||
});
|
||||
30
.github/workflows/claude.yml
vendored
30
.github/workflows/claude.yml
vendored
@@ -13,35 +13,23 @@ on:
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
github.repository == 'oven-sh/bun' &&
|
||||
(
|
||||
(github.event_name == 'issue_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'pull_request_review' && (github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'issues' && (github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'COLLABORATOR'))
|
||||
) &&
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: claude
|
||||
env:
|
||||
IS_SANDBOX: 1
|
||||
container:
|
||||
image: claude-bun:latest
|
||||
options: --privileged
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- run: cd /workspace/bun
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
# TODO: switch this out once they merge their v1
|
||||
uses: km-anthropic/claude-code-action@v1-dev
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
timeout_minutes: "180"
|
||||
claude_args: |
|
||||
--dangerously-skip-permissions
|
||||
--system-prompt "You are working on the Bun codebase"
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -6,8 +6,6 @@ on:
|
||||
- "docs/**"
|
||||
- "packages/bun-types/**.d.ts"
|
||||
- "CONTRIBUTING.md"
|
||||
- "src/cli/install.sh"
|
||||
- "src/cli/install.ps1"
|
||||
branches:
|
||||
- main
|
||||
|
||||
|
||||
82
.github/workflows/format.yml
vendored
82
.github/workflows/format.yml
vendored
@@ -37,72 +37,24 @@ jobs:
|
||||
- name: Setup Dependencies
|
||||
run: |
|
||||
bun install
|
||||
- name: Format Code
|
||||
- name: Install LLVM
|
||||
run: |
|
||||
# Start prettier in background with prefixed output
|
||||
echo "::group::Prettier"
|
||||
(bun run prettier 2>&1 | sed 's/^/[prettier] /' || echo "[prettier] Failed with exit code $?") &
|
||||
PRETTIER_PID=$!
|
||||
|
||||
# Start clang-format installation and formatting in background with prefixed output
|
||||
echo "::group::Clang-format"
|
||||
(
|
||||
echo "[clang-format] Installing clang-format-${{ env.LLVM_VERSION_MAJOR }}..."
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null
|
||||
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${{ env.LLVM_VERSION_MAJOR }} main" | sudo tee /etc/apt/sources.list.d/llvm.list > /dev/null
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq --no-install-recommends --no-install-suggests -o=Dpkg::Use-Pty=0 clang-format-${{ env.LLVM_VERSION_MAJOR }}
|
||||
echo "[clang-format] Running clang-format..."
|
||||
LLVM_VERSION_MAJOR=${{ env.LLVM_VERSION_MAJOR }} ./scripts/run-clang-format.sh format 2>&1 | sed 's/^/[clang-format] /'
|
||||
) &
|
||||
CLANG_PID=$!
|
||||
|
||||
# Setup Zig in temp directory and run zig fmt in background with prefixed output
|
||||
echo "::group::Zig fmt"
|
||||
(
|
||||
ZIG_TEMP=$(mktemp -d)
|
||||
echo "[zig] Downloading Zig (musl build)..."
|
||||
wget -q -O "$ZIG_TEMP/zig.zip" https://github.com/oven-sh/zig/releases/download/autobuild-d1a4e0b0ddc75f37c6a090b97eef0cbb6335556e/bootstrap-x86_64-linux-musl.zip
|
||||
unzip -q -d "$ZIG_TEMP" "$ZIG_TEMP/zig.zip"
|
||||
export PATH="$ZIG_TEMP/bootstrap-x86_64-linux-musl:$PATH"
|
||||
echo "[zig] Running zig fmt..."
|
||||
zig fmt src 2>&1 | sed 's/^/[zig] /'
|
||||
./scripts/sort-imports.ts src 2>&1 | sed 's/^/[zig] /'
|
||||
zig fmt src 2>&1 | sed 's/^/[zig] /'
|
||||
rm -rf "$ZIG_TEMP"
|
||||
) &
|
||||
ZIG_PID=$!
|
||||
|
||||
# Wait for all formatting tasks to complete
|
||||
echo ""
|
||||
echo "Running formatters in parallel..."
|
||||
FAILED=0
|
||||
|
||||
if ! wait $PRETTIER_PID; then
|
||||
echo "::error::Prettier failed"
|
||||
FAILED=1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
if ! wait $CLANG_PID; then
|
||||
echo "::error::Clang-format failed"
|
||||
FAILED=1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
if ! wait $ZIG_PID; then
|
||||
echo "::error::Zig fmt failed"
|
||||
FAILED=1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
# Exit with error if any formatter failed
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "::error::One or more formatters failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ All formatters completed successfully"
|
||||
curl -fsSL https://apt.llvm.org/llvm.sh | sudo bash -s -- ${{ env.LLVM_VERSION_MAJOR }} all
|
||||
- name: Setup Zig
|
||||
uses: mlugg/setup-zig@v1
|
||||
with:
|
||||
version: 0.14.0
|
||||
- name: Zig Format
|
||||
run: |
|
||||
zig fmt src
|
||||
./scripts/sort-imports.ts src
|
||||
zig fmt src
|
||||
- name: Prettier Format
|
||||
run: |
|
||||
bun run prettier
|
||||
- name: Clang Format
|
||||
run: |
|
||||
bun run clang-format
|
||||
- name: Ban Words
|
||||
run: |
|
||||
bun ./test/internal/ban-words.test.ts
|
||||
|
||||
14
CLAUDE.md
14
CLAUDE.md
@@ -43,12 +43,7 @@ Tests use Bun's Jest-compatible test runner with proper test fixtures:
|
||||
|
||||
```typescript
|
||||
import { test, expect } from "bun:test";
|
||||
import {
|
||||
bunEnv,
|
||||
bunExe,
|
||||
normalizeBunSnapshot,
|
||||
tempDirWithFiles,
|
||||
} from "harness";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
test("my feature", async () => {
|
||||
// Create temp directory with test files
|
||||
@@ -61,7 +56,6 @@ test("my feature", async () => {
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
@@ -71,14 +65,11 @@ test("my feature", async () => {
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Prefer snapshot tests over expect(stdout).toBe("hello\n");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"hello"`);
|
||||
expect(stdout).toBe("hello\n");
|
||||
});
|
||||
```
|
||||
|
||||
- Always use `port: 0`. Do not hardcode ports. Do not use your own random port number function.
|
||||
- Use `normalizeBunSnapshot` to normalize snapshot output of the test.
|
||||
- NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. That is NOT a valid test.
|
||||
|
||||
## Code Architecture
|
||||
|
||||
@@ -240,7 +231,6 @@ bun ci
|
||||
9. **Cross-platform** - Run `bun run zig:check-all` to compile the Zig code on all platforms when making platform-specific changes
|
||||
10. **Debug builds** - Use `BUN_DEBUG_QUIET_LOGS=1` to disable debug logging, or `BUN_DEBUG_<scope>=1` to enable specific scopes
|
||||
11. **Be humble & honest** - NEVER overstate what you got done or what actually works in commits, PRs or in messages to the user.
|
||||
12. **Branch names must start with `claude/`** - This is a requirement for the CI to work.
|
||||
|
||||
## Key APIs and Features
|
||||
|
||||
|
||||
@@ -15,13 +15,11 @@
|
||||
"eventemitter3": "^5.0.0",
|
||||
"execa": "^8.0.1",
|
||||
"fast-glob": "3.3.1",
|
||||
"fastify": "^5.0.0",
|
||||
"fdir": "^6.1.0",
|
||||
"mitata": "^1.0.25",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"string-width": "7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zx": "^7.2.3",
|
||||
},
|
||||
@@ -95,18 +93,6 @@
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.14.54", "", { "os": "linux", "cpu": "none" }, "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw=="],
|
||||
|
||||
"@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.2", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ=="],
|
||||
|
||||
"@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="],
|
||||
|
||||
"@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.0.3", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="],
|
||||
|
||||
"@fastify/forwarded": ["@fastify/forwarded@3.0.0", "", {}, "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA=="],
|
||||
|
||||
"@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="],
|
||||
|
||||
"@fastify/proxy-addr": ["@fastify/proxy-addr@5.0.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.1.1", "", { "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.0", "", {}, "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="],
|
||||
@@ -157,20 +143,10 @@
|
||||
|
||||
"@types/which": ["@types/which@3.0.3", "", {}, "sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g=="],
|
||||
|
||||
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
|
||||
|
||||
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||
|
||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
|
||||
|
||||
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
||||
|
||||
"avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
|
||||
|
||||
"benchmark": ["benchmark@2.1.4", "", { "dependencies": { "lodash": "^4.17.4", "platform": "^1.3.3" } }, "sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ=="],
|
||||
|
||||
"braces": ["braces@3.0.2", "", { "dependencies": { "fill-range": "^7.0.1" } }, "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A=="],
|
||||
@@ -191,16 +167,12 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
||||
|
||||
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
|
||||
|
||||
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
||||
@@ -261,22 +233,10 @@
|
||||
|
||||
"execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
||||
|
||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
|
||||
|
||||
"fast-json-stringify": ["fast-json-stringify@6.0.1", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^2.0.0", "rfdc": "^1.2.0" } }, "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg=="],
|
||||
|
||||
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
|
||||
|
||||
"fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="],
|
||||
|
||||
"fastify": ["fastify@5.5.0", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.0.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-ZWSWlzj3K/DcULCnCjEiC2zn2FBPdlZsSA/pnPa/dbUfLvxkD/Nqmb0XXMXLrWkeM4uQPUvjdJpwtXmTfriXqw=="],
|
||||
|
||||
"fastq": ["fastq@1.15.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw=="],
|
||||
|
||||
"fdir": ["fdir@6.1.0", "", { "peerDependencies": { "picomatch": "2.x" } }, "sha512-274qhz5PxNnA/fybOu6apTCUnM0GnO3QazB6VH+oag/7DQskdYq8lm07ZSm90kEQuWYH5GvjAxGruuHrEr0bcg=="],
|
||||
@@ -285,8 +245,6 @@
|
||||
|
||||
"fill-range": ["fill-range@7.0.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ=="],
|
||||
|
||||
"find-my-way": ["find-my-way@9.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"from": ["from@0.1.7", "", {}, "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g=="],
|
||||
@@ -315,8 +273,6 @@
|
||||
|
||||
"ignore": ["ignore@5.3.0", "", {}, "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
@@ -333,16 +289,10 @@
|
||||
|
||||
"jsesc": ["jsesc@2.5.2", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="],
|
||||
|
||||
"json-schema-ref-resolver": ["json-schema-ref-resolver@2.0.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
|
||||
|
||||
"light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
|
||||
|
||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
@@ -373,8 +323,6 @@
|
||||
|
||||
"npm-run-path": ["npm-run-path@5.2.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg=="],
|
||||
|
||||
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
||||
|
||||
"onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
@@ -387,50 +335,24 @@
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"pino": ["pino@9.9.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-zxsRIQG9HzG+jEljmvmZupOMDUQ0Jpj0yAgE28jQvvrdYTlEaiGwelJpdndMl/MBuRr70heIj83QyqJUWaU8mQ=="],
|
||||
|
||||
"pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
|
||||
|
||||
"pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="],
|
||||
|
||||
"platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="],
|
||||
|
||||
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
||||
|
||||
"ps-tree": ["ps-tree@1.2.0", "", { "dependencies": { "event-stream": "=3.3.4" }, "bin": { "ps-tree": "./bin/ps-tree.js" } }, "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||
|
||||
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
||||
|
||||
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
|
||||
|
||||
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
|
||||
|
||||
"reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safe-regex2": ["safe-regex2@5.0.0", "", { "dependencies": { "ret": "~0.5.0" } }, "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw=="],
|
||||
|
||||
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"secure-json-parse": ["secure-json-parse@4.0.0", "", {}, "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA=="],
|
||||
|
||||
"semver": ["semver@6.3.0", "", { "bin": { "semver": "./bin/semver.js" } }, "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
@@ -441,12 +363,8 @@
|
||||
|
||||
"slash": ["slash@4.0.0", "", {}, "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew=="],
|
||||
|
||||
"sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
|
||||
|
||||
"split": ["split@0.3.3", "", { "dependencies": { "through": "2" } }, "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"stream-combiner": ["stream-combiner@0.0.4", "", { "dependencies": { "duplexer": "~0.1.1" } }, "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw=="],
|
||||
|
||||
"string-width": ["string-width@7.1.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw=="],
|
||||
@@ -457,8 +375,6 @@
|
||||
|
||||
"supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
|
||||
|
||||
"thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
|
||||
|
||||
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
|
||||
|
||||
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
|
||||
@@ -467,8 +383,6 @@
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
|
||||
|
||||
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
@@ -493,14 +407,8 @@
|
||||
|
||||
"ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"avvio/fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"fastify/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"string-width": "7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zx": "^7.2.3"
|
||||
},
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import npmStripAnsi from "strip-ansi";
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
let bunStripANSI = null;
|
||||
if (!process.env.FORCE_NPM) {
|
||||
bunStripANSI = globalThis?.Bun?.stripANSI;
|
||||
}
|
||||
|
||||
const stripANSI = bunStripANSI || npmStripAnsi;
|
||||
const formatter = new Intl.NumberFormat();
|
||||
const format = n => {
|
||||
return formatter.format(n);
|
||||
};
|
||||
|
||||
const inputs = [
|
||||
["hello world", "no-ansi"],
|
||||
["\x1b[31mred\x1b[39m", "ansi"],
|
||||
["a".repeat(1024 * 16), "long-no-ansi"],
|
||||
["\x1b[31mred\x1b[39m".repeat(1024 * 16), "long-ansi"],
|
||||
];
|
||||
|
||||
const maxInputLength = Math.max(...inputs.map(([input]) => input.length));
|
||||
|
||||
for (const [input, textLabel] of inputs) {
|
||||
const label = bunStripANSI ? "Bun.stripANSI" : "npm/strip-ansi";
|
||||
const name = `${label} ${format(input.length).padStart(format(maxInputLength).length, " ")} chars ${textLabel}`;
|
||||
|
||||
bench(name, () => {
|
||||
stripANSI(input);
|
||||
});
|
||||
|
||||
if (bunStripANSI && bunStripANSI(input) !== npmStripAnsi(input)) {
|
||||
throw new Error("strip-ansi mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
await run();
|
||||
12
bun.lock
12
bun.lock
@@ -39,8 +39,8 @@
|
||||
},
|
||||
},
|
||||
"overrides": {
|
||||
"bun-types": "workspace:packages/bun-types",
|
||||
"@types/bun": "workspace:packages/@types/bun",
|
||||
"bun-types": "workspace:packages/bun-types",
|
||||
},
|
||||
"packages": {
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
|
||||
|
||||
"@sentry/types": ["@sentry/types@7.120.4", "", {}, "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q=="],
|
||||
"@sentry/types": ["@sentry/types@7.120.3", "", {}, "sha512-C4z+3kGWNFJ303FC+FxAd4KkHvxpNFYAFN8iMIgBwJdpIl25KZ8Q/VdGn0MLLUEHNLvjob0+wvwlcRBBNLXOow=="],
|
||||
|
||||
"@types/aws-lambda": ["@types/aws-lambda@8.10.152", "", {}, "sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw=="],
|
||||
|
||||
@@ -159,9 +159,9 @@
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
|
||||
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="],
|
||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||
|
||||
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
|
||||
|
||||
@@ -311,7 +311,7 @@
|
||||
|
||||
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
||||
"universal-github-app-jwt": ["universal-github-app-jwt@1.2.0", "", { "dependencies": { "@types/jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2" } }, "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g=="],
|
||||
|
||||
@@ -333,6 +333,8 @@
|
||||
|
||||
"@octokit/webhooks/@octokit/webhooks-methods": ["@octokit/webhooks-methods@4.1.0", "", {}, "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ=="],
|
||||
|
||||
"bun-tracestrings/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"camel-case/no-case": ["no-case@2.3.2", "", { "dependencies": { "lower-case": "^1.1.1" } }, "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ=="],
|
||||
|
||||
"change-case/camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="],
|
||||
|
||||
@@ -3,7 +3,6 @@ packages/bun-usockets/src/crypto/sni_tree.cpp
|
||||
src/bake/BakeGlobalObject.cpp
|
||||
src/bake/BakeProduction.cpp
|
||||
src/bake/BakeSourceProvider.cpp
|
||||
src/bake/DevServerSourceProvider.cpp
|
||||
src/bun.js/bindings/ActiveDOMCallback.cpp
|
||||
src/bun.js/bindings/AsymmetricKeyValue.cpp
|
||||
src/bun.js/bindings/AsyncContextFrame.cpp
|
||||
@@ -43,7 +42,6 @@ src/bun.js/bindings/DOMURL.cpp
|
||||
src/bun.js/bindings/DOMWrapperWorld.cpp
|
||||
src/bun.js/bindings/DoubleFormatter.cpp
|
||||
src/bun.js/bindings/EncodeURIComponent.cpp
|
||||
src/bun.js/bindings/EncodingTables.cpp
|
||||
src/bun.js/bindings/ErrorCode.cpp
|
||||
src/bun.js/bindings/ErrorStackFrame.cpp
|
||||
src/bun.js/bindings/ErrorStackTrace.cpp
|
||||
@@ -194,16 +192,7 @@ src/bun.js/bindings/ServerRouteList.cpp
|
||||
src/bun.js/bindings/spawn.cpp
|
||||
src/bun.js/bindings/SQLClient.cpp
|
||||
src/bun.js/bindings/sqlite/JSSQLStatement.cpp
|
||||
src/bun.js/bindings/stripANSI.cpp
|
||||
src/bun.js/bindings/Strong.cpp
|
||||
src/bun.js/bindings/TextCodec.cpp
|
||||
src/bun.js/bindings/TextCodecCJK.cpp
|
||||
src/bun.js/bindings/TextCodecReplacement.cpp
|
||||
src/bun.js/bindings/TextCodecSingleByte.cpp
|
||||
src/bun.js/bindings/TextCodecUserDefined.cpp
|
||||
src/bun.js/bindings/TextCodecWrapper.cpp
|
||||
src/bun.js/bindings/TextEncoding.cpp
|
||||
src/bun.js/bindings/TextEncodingRegistry.cpp
|
||||
src/bun.js/bindings/Uint8Array.cpp
|
||||
src/bun.js/bindings/Undici.cpp
|
||||
src/bun.js/bindings/URLDecomposition.cpp
|
||||
@@ -392,7 +381,6 @@ src/bun.js/bindings/webcore/ReadableStreamDefaultController.cpp
|
||||
src/bun.js/bindings/webcore/ReadableStreamSink.cpp
|
||||
src/bun.js/bindings/webcore/ReadableStreamSource.cpp
|
||||
src/bun.js/bindings/webcore/ResourceTiming.cpp
|
||||
src/bun.js/bindings/webcore/ResponseHelpers.cpp
|
||||
src/bun.js/bindings/webcore/RFC7230.cpp
|
||||
src/bun.js/bindings/webcore/SerializedScriptValue.cpp
|
||||
src/bun.js/bindings/webcore/ServerTiming.cpp
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
src/js/builtins.d.ts
|
||||
src/js/builtins/Bake.ts
|
||||
src/js/builtins/BakeSSRResponse.ts
|
||||
src/js/builtins/BundlerPlugin.ts
|
||||
src/js/builtins/ByteLengthQueuingStrategy.ts
|
||||
src/js/builtins/CommonJS.ts
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
src/allocators.zig
|
||||
src/allocators/AllocationScope.zig
|
||||
src/allocators/basic.zig
|
||||
src/allocators/fallback.zig
|
||||
src/allocators/fallback/z.zig
|
||||
src/allocators/LinuxMemFdAllocator.zig
|
||||
src/allocators/MaxHeapAllocator.zig
|
||||
src/allocators/MemoryReportingAllocator.zig
|
||||
@@ -21,43 +19,19 @@ src/ast/base.zig
|
||||
src/ast/Binding.zig
|
||||
src/ast/BundledAst.zig
|
||||
src/ast/CharFreq.zig
|
||||
src/ast/ConvertESMExportsForHmr.zig
|
||||
src/ast/E.zig
|
||||
src/ast/Expr.zig
|
||||
src/ast/foldStringAddition.zig
|
||||
src/ast/G.zig
|
||||
src/ast/ImportScanner.zig
|
||||
src/ast/KnownGlobal.zig
|
||||
src/ast/Macro.zig
|
||||
src/ast/maybe.zig
|
||||
src/ast/NewStore.zig
|
||||
src/ast/Op.zig
|
||||
src/ast/P.zig
|
||||
src/ast/parse.zig
|
||||
src/ast/parseFn.zig
|
||||
src/ast/parseImportExport.zig
|
||||
src/ast/parseJSXElement.zig
|
||||
src/ast/parsePrefix.zig
|
||||
src/ast/parseProperty.zig
|
||||
src/ast/Parser.zig
|
||||
src/ast/parseStmt.zig
|
||||
src/ast/parseSuffix.zig
|
||||
src/ast/parseTypescript.zig
|
||||
src/ast/S.zig
|
||||
src/ast/Scope.zig
|
||||
src/ast/ServerComponentBoundary.zig
|
||||
src/ast/SideEffects.zig
|
||||
src/ast/skipTypescript.zig
|
||||
src/ast/Stmt.zig
|
||||
src/ast/Symbol.zig
|
||||
src/ast/symbols.zig
|
||||
src/ast/TS.zig
|
||||
src/ast/TypeScript.zig
|
||||
src/ast/UseDirective.zig
|
||||
src/ast/visit.zig
|
||||
src/ast/visitBinaryExpression.zig
|
||||
src/ast/visitExpr.zig
|
||||
src/ast/visitStmt.zig
|
||||
src/async/posix_event_loop.zig
|
||||
src/async/stub_event_loop.zig
|
||||
src/async/windows_event_loop.zig
|
||||
@@ -98,11 +72,6 @@ src/bun.js/api/bun/spawn.zig
|
||||
src/bun.js/api/bun/spawn/stdio.zig
|
||||
src/bun.js/api/bun/ssl_wrapper.zig
|
||||
src/bun.js/api/bun/subprocess.zig
|
||||
src/bun.js/api/bun/subprocess/Readable.zig
|
||||
src/bun.js/api/bun/subprocess/ResourceUsage.zig
|
||||
src/bun.js/api/bun/subprocess/StaticPipeWriter.zig
|
||||
src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
|
||||
src/bun.js/api/bun/subprocess/Writable.zig
|
||||
src/bun.js/api/bun/udp_socket.zig
|
||||
src/bun.js/api/bun/x509.zig
|
||||
src/bun.js/api/BunObject.zig
|
||||
@@ -135,7 +104,6 @@ src/bun.js/api/server/StaticRoute.zig
|
||||
src/bun.js/api/server/WebSocketServerContext.zig
|
||||
src/bun.js/api/streams.classes.zig
|
||||
src/bun.js/api/Timer.zig
|
||||
src/bun.js/api/Timer/DateHeaderTimer.zig
|
||||
src/bun.js/api/Timer/EventLoopTimer.zig
|
||||
src/bun.js/api/Timer/ImmediateObject.zig
|
||||
src/bun.js/api/Timer/TimeoutObject.zig
|
||||
@@ -197,7 +165,6 @@ src/bun.js/bindings/SourceProvider.zig
|
||||
src/bun.js/bindings/SourceType.zig
|
||||
src/bun.js/bindings/static_export.zig
|
||||
src/bun.js/bindings/SystemError.zig
|
||||
src/bun.js/bindings/TextCodec.zig
|
||||
src/bun.js/bindings/URL.zig
|
||||
src/bun.js/bindings/URLSearchParams.zig
|
||||
src/bun.js/bindings/VM.zig
|
||||
@@ -283,84 +250,7 @@ src/bun.js/RuntimeTranspilerCache.zig
|
||||
src/bun.js/SavedSourceMap.zig
|
||||
src/bun.js/Strong.zig
|
||||
src/bun.js/test/diff_format.zig
|
||||
src/bun.js/test/diff/diff_match_patch.zig
|
||||
src/bun.js/test/diff/printDiff.zig
|
||||
src/bun.js/test/expect.zig
|
||||
src/bun.js/test/expect/toBe.zig
|
||||
src/bun.js/test/expect/toBeArray.zig
|
||||
src/bun.js/test/expect/toBeArrayOfSize.zig
|
||||
src/bun.js/test/expect/toBeBoolean.zig
|
||||
src/bun.js/test/expect/toBeCloseTo.zig
|
||||
src/bun.js/test/expect/toBeDate.zig
|
||||
src/bun.js/test/expect/toBeDefined.zig
|
||||
src/bun.js/test/expect/toBeEmpty.zig
|
||||
src/bun.js/test/expect/toBeEmptyObject.zig
|
||||
src/bun.js/test/expect/toBeEven.zig
|
||||
src/bun.js/test/expect/toBeFalse.zig
|
||||
src/bun.js/test/expect/toBeFalsy.zig
|
||||
src/bun.js/test/expect/toBeFinite.zig
|
||||
src/bun.js/test/expect/toBeFunction.zig
|
||||
src/bun.js/test/expect/toBeGreaterThan.zig
|
||||
src/bun.js/test/expect/toBeGreaterThanOrEqual.zig
|
||||
src/bun.js/test/expect/toBeInstanceOf.zig
|
||||
src/bun.js/test/expect/toBeInteger.zig
|
||||
src/bun.js/test/expect/toBeLessThan.zig
|
||||
src/bun.js/test/expect/toBeLessThanOrEqual.zig
|
||||
src/bun.js/test/expect/toBeNaN.zig
|
||||
src/bun.js/test/expect/toBeNegative.zig
|
||||
src/bun.js/test/expect/toBeNil.zig
|
||||
src/bun.js/test/expect/toBeNull.zig
|
||||
src/bun.js/test/expect/toBeNumber.zig
|
||||
src/bun.js/test/expect/toBeObject.zig
|
||||
src/bun.js/test/expect/toBeOdd.zig
|
||||
src/bun.js/test/expect/toBeOneOf.zig
|
||||
src/bun.js/test/expect/toBePositive.zig
|
||||
src/bun.js/test/expect/toBeString.zig
|
||||
src/bun.js/test/expect/toBeSymbol.zig
|
||||
src/bun.js/test/expect/toBeTrue.zig
|
||||
src/bun.js/test/expect/toBeTruthy.zig
|
||||
src/bun.js/test/expect/toBeTypeOf.zig
|
||||
src/bun.js/test/expect/toBeUndefined.zig
|
||||
src/bun.js/test/expect/toBeValidDate.zig
|
||||
src/bun.js/test/expect/toBeWithin.zig
|
||||
src/bun.js/test/expect/toContain.zig
|
||||
src/bun.js/test/expect/toContainAllKeys.zig
|
||||
src/bun.js/test/expect/toContainAllValues.zig
|
||||
src/bun.js/test/expect/toContainAnyKeys.zig
|
||||
src/bun.js/test/expect/toContainAnyValues.zig
|
||||
src/bun.js/test/expect/toContainEqual.zig
|
||||
src/bun.js/test/expect/toContainKey.zig
|
||||
src/bun.js/test/expect/toContainKeys.zig
|
||||
src/bun.js/test/expect/toContainValue.zig
|
||||
src/bun.js/test/expect/toContainValues.zig
|
||||
src/bun.js/test/expect/toEndWith.zig
|
||||
src/bun.js/test/expect/toEqual.zig
|
||||
src/bun.js/test/expect/toEqualIgnoringWhitespace.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalled.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledOnce.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledTimes.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveBeenLastCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveBeenNthCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveLastReturnedWith.zig
|
||||
src/bun.js/test/expect/toHaveLength.zig
|
||||
src/bun.js/test/expect/toHaveNthReturnedWith.zig
|
||||
src/bun.js/test/expect/toHaveProperty.zig
|
||||
src/bun.js/test/expect/toHaveReturned.zig
|
||||
src/bun.js/test/expect/toHaveReturnedTimes.zig
|
||||
src/bun.js/test/expect/toHaveReturnedWith.zig
|
||||
src/bun.js/test/expect/toInclude.zig
|
||||
src/bun.js/test/expect/toIncludeRepeated.zig
|
||||
src/bun.js/test/expect/toMatch.zig
|
||||
src/bun.js/test/expect/toMatchInlineSnapshot.zig
|
||||
src/bun.js/test/expect/toMatchObject.zig
|
||||
src/bun.js/test/expect/toMatchSnapshot.zig
|
||||
src/bun.js/test/expect/toSatisfy.zig
|
||||
src/bun.js/test/expect/toStartWith.zig
|
||||
src/bun.js/test/expect/toStrictEqual.zig
|
||||
src/bun.js/test/expect/toThrow.zig
|
||||
src/bun.js/test/expect/toThrowErrorMatchingInlineSnapshot.zig
|
||||
src/bun.js/test/expect/toThrowErrorMatchingSnapshot.zig
|
||||
src/bun.js/test/jest.zig
|
||||
src/bun.js/test/pretty_format.zig
|
||||
src/bun.js/test/snapshot.zig
|
||||
@@ -597,6 +487,7 @@ src/defines.zig
|
||||
src/deps/boringssl.translated.zig
|
||||
src/deps/brotli_c.zig
|
||||
src/deps/c_ares.zig
|
||||
src/deps/diffz/DiffMatchPatch.zig
|
||||
src/deps/libdeflate.zig
|
||||
src/deps/libuv.zig
|
||||
src/deps/lol-html.zig
|
||||
@@ -791,9 +682,6 @@ src/Progress.zig
|
||||
src/ptr.zig
|
||||
src/ptr/Cow.zig
|
||||
src/ptr/CowSlice.zig
|
||||
src/ptr/meta.zig
|
||||
src/ptr/owned.zig
|
||||
src/ptr/owned/maybe.zig
|
||||
src/ptr/ref_count.zig
|
||||
src/ptr/tagged_pointer.zig
|
||||
src/ptr/weak_ptr.zig
|
||||
@@ -818,10 +706,8 @@ src/s3/multipart.zig
|
||||
src/s3/simple_request.zig
|
||||
src/s3/storage_class.zig
|
||||
src/safety.zig
|
||||
src/safety/alloc.zig
|
||||
src/safety/alloc_ptr.zig
|
||||
src/safety/CriticalSection.zig
|
||||
src/safety/thread_id.zig
|
||||
src/safety/ThreadLock.zig
|
||||
src/semver.zig
|
||||
src/semver/ExternalString.zig
|
||||
src/semver/SemverObject.zig
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
oven-sh/mimalloc
|
||||
COMMIT
|
||||
1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a
|
||||
178534eeb7c0b4e2f438b513640c6f4d7338416a
|
||||
)
|
||||
|
||||
set(MIMALLOC_CMAKE_ARGS
|
||||
@@ -39,23 +39,18 @@ set(MIMALLOC_CMAKE_ARGS
|
||||
-DMI_NO_THP=1
|
||||
)
|
||||
|
||||
if (ABI STREQUAL "musl")
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_LIBC_MUSL=ON)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ASAN)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_TRACK_ASAN=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_DEBUG_UBSAN=ON)
|
||||
elseif(APPLE OR LINUX)
|
||||
# Enable static override when ASAN is not enabled
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=ON)
|
||||
if(APPLE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=ON)
|
||||
else()
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
endif()
|
||||
@@ -69,19 +64,7 @@ if(ENABLE_VALGRIND)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_VALGRIND=ON)
|
||||
endif()
|
||||
|
||||
# Enable SIMD optimizations when not building for baseline (older CPUs)
|
||||
if(NOT ENABLE_BASELINE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_ARCH=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_SIMD=ON)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
if(DEBUG)
|
||||
set(MIMALLOC_LIBRARY mimalloc-static-debug)
|
||||
else()
|
||||
set(MIMALLOC_LIBRARY mimalloc-static)
|
||||
endif()
|
||||
elseif(DEBUG)
|
||||
if(DEBUG)
|
||||
if (ENABLE_ASAN)
|
||||
set(MIMALLOC_LIBRARY mimalloc-asan-debug)
|
||||
else()
|
||||
|
||||
@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
|
||||
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
|
||||
|
||||
if(NOT WEBKIT_VERSION)
|
||||
set(WEBKIT_VERSION aa4997abc9126f5a7557c9ecb7e8104779d87ec4)
|
||||
set(WEBKIT_VERSION 642e2252f6298387edb6d2f991a0408fd0320466)
|
||||
endif()
|
||||
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -320,6 +320,7 @@ Bun automatically sets the `Content-Type` header for request bodies when not exp
|
||||
|
||||
- For `Blob` objects, uses the blob's `type`
|
||||
- For `FormData`, sets appropriate multipart boundary
|
||||
- For JSON objects, sets `application/json`
|
||||
|
||||
## Debugging
|
||||
|
||||
|
||||
@@ -772,65 +772,6 @@ console.log(obj); // => { foo: "bar" }
|
||||
|
||||
Internally, [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) serialize and deserialize the same way. This exposes the underlying [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to JavaScript as an ArrayBuffer.
|
||||
|
||||
## `Bun.stripANSI()` ~6-57x faster `strip-ansi` alternative
|
||||
|
||||
`Bun.stripANSI(text: string): string`
|
||||
|
||||
Strip ANSI escape codes from a string. This is useful for removing colors and formatting from terminal output.
|
||||
|
||||
```ts
|
||||
const coloredText = "\u001b[31mHello\u001b[0m \u001b[32mWorld\u001b[0m";
|
||||
const plainText = Bun.stripANSI(coloredText);
|
||||
console.log(plainText); // => "Hello World"
|
||||
|
||||
// Works with various ANSI codes
|
||||
const formatted = "\u001b[1m\u001b[4mBold and underlined\u001b[0m";
|
||||
console.log(Bun.stripANSI(formatted)); // => "Bold and underlined"
|
||||
```
|
||||
|
||||
`Bun.stripANSI` is significantly faster than the popular [`strip-ansi`](https://www.npmjs.com/package/strip-ansi) npm package:
|
||||
|
||||
```js
|
||||
> bun bench/snippets/strip-ansi.mjs
|
||||
cpu: Apple M3 Max
|
||||
runtime: bun 1.2.21 (arm64-darwin)
|
||||
|
||||
benchmark avg (min … max) p75 / p99
|
||||
------------------------------------------------------- ----------
|
||||
Bun.stripANSI 11 chars no-ansi 8.13 ns/iter 8.27 ns
|
||||
(7.45 ns … 33.59 ns) 10.29 ns
|
||||
|
||||
Bun.stripANSI 13 chars ansi 51.68 ns/iter 52.51 ns
|
||||
(46.16 ns … 113.71 ns) 57.71 ns
|
||||
|
||||
Bun.stripANSI 16,384 chars long-no-ansi 298.39 ns/iter 305.44 ns
|
||||
(281.50 ns … 331.65 ns) 320.70 ns
|
||||
|
||||
Bun.stripANSI 212,992 chars long-ansi 227.65 µs/iter 234.50 µs
|
||||
(216.46 µs … 401.92 µs) 262.25 µs
|
||||
```
|
||||
|
||||
```js
|
||||
> node bench/snippets/strip-ansi.mjs
|
||||
cpu: Apple M3 Max
|
||||
runtime: node 24.6.0 (arm64-darwin)
|
||||
|
||||
benchmark avg (min … max) p75 / p99
|
||||
-------------------------------------------------------- ---------
|
||||
npm/strip-ansi 11 chars no-ansi 466.79 ns/iter 468.67 ns
|
||||
(454.08 ns … 570.67 ns) 543.67 ns
|
||||
|
||||
npm/strip-ansi 13 chars ansi 546.77 ns/iter 550.23 ns
|
||||
(532.74 ns … 651.08 ns) 590.35 ns
|
||||
|
||||
npm/strip-ansi 16,384 chars long-no-ansi 4.85 µs/iter 4.89 µs
|
||||
(4.71 µs … 5.00 µs) 4.98 µs
|
||||
|
||||
npm/strip-ansi 212,992 chars long-ansi 1.36 ms/iter 1.38 ms
|
||||
(1.27 ms … 1.73 ms) 1.49 ms
|
||||
|
||||
```
|
||||
|
||||
## `estimateShallowMemoryUsageOf` in `bun:jsc`
|
||||
|
||||
The `estimateShallowMemoryUsageOf` function returns a best-effort estimate of the memory usage of an object in bytes, excluding the memory usage of properties or other objects it references. For accurate per-object memory usage, use `Bun.generateHeapSnapshot`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% callout %}
|
||||
**🚧** — The `Worker` API is still experimental (particularly for terminating workers). We are actively working on improving this.
|
||||
**🚧** — The `Worker` API is still experimental and should not be considered ready for production.
|
||||
{% /callout %}
|
||||
|
||||
[`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) lets you start and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread.
|
||||
|
||||
@@ -496,36 +496,6 @@ Whether to generate a non-Bun lockfile alongside `bun.lock`. (A `bun.lock` will
|
||||
print = "yarn"
|
||||
```
|
||||
|
||||
### `install.linker`
|
||||
|
||||
Configure the default linker strategy. Default `"hoisted"`.
|
||||
|
||||
For complete documentation refer to [Package manager > Isolated installs](https://bun.com/docs/install/isolated).
|
||||
|
||||
```toml
|
||||
[install]
|
||||
linker = "hoisted"
|
||||
```
|
||||
|
||||
Valid values are:
|
||||
|
||||
{% table %}
|
||||
|
||||
- Value
|
||||
- Description
|
||||
|
||||
---
|
||||
|
||||
- `"hoisted"`
|
||||
- Link dependencies in a shared `node_modules` directory.
|
||||
|
||||
---
|
||||
|
||||
- `"isolated"`
|
||||
- Link dependencies inside each package installation.
|
||||
|
||||
{% /table %}
|
||||
|
||||
<!-- ## Debugging -->
|
||||
|
||||
<!--
|
||||
|
||||
@@ -148,7 +148,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
### [`node:vm`](https://nodejs.org/api/vm.html)
|
||||
|
||||
🟡 Core functionality and ES modules are implemented, including `vm.Script`, `vm.createContext`, `vm.runInContext`, `vm.runInNewContext`, `vm.runInThisContext`, `vm.compileFunction`, `vm.isContext`, `vm.Module`, `vm.SourceTextModule`, `vm.SyntheticModule`, and `importModuleDynamically` support. Options like `timeout` and `breakOnSigint` are fully supported. Missing `vm.measureMemory` and some `cachedData` functionality.
|
||||
🟡 Core functionality works, but experimental VM ES modules are not implemented, including `vm.Module`, `vm.SourceTextModule`, `vm.SyntheticModule`,`importModuleDynamically`, and `vm.measureMemory`. Options like `timeout`, `breakOnSigint`, `cachedData` are not implemented yet.
|
||||
|
||||
### [`node:wasi`](https://nodejs.org/api/wasi.html)
|
||||
|
||||
@@ -214,10 +214,6 @@ The table below lists all globals implemented by Node.js and Bun's current compa
|
||||
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`Atomics`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics)
|
||||
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel)
|
||||
|
||||
🟢 Fully implemented.
|
||||
|
||||
@@ -532,74 +532,6 @@ Hello World! pwd=C:\Users\Demo
|
||||
|
||||
Bun Shell is a small programming language in Bun that is implemented in Zig. It includes a handwritten lexer, parser, and interpreter. Unlike bash, zsh, and other shells, Bun Shell runs operations concurrently.
|
||||
|
||||
## Security in the Bun shell
|
||||
|
||||
By design, the Bun shell _does not invoke a system shell_ (like `/bin/sh`) and
|
||||
is instead a re-implementation of bash that runs in the same Bun process,
|
||||
designed with security in mind.
|
||||
|
||||
When parsing command arguments, it treats all _interpolated variables_ as single, literal strings.
|
||||
|
||||
This protects the Bun shell against **command injection**:
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const userInput = "my-file.txt; rm -rf /";
|
||||
|
||||
// SAFE: `userInput` is treated as a single quoted string
|
||||
await $`ls ${userInput}`;
|
||||
```
|
||||
|
||||
In the above example, `userInput` is treated as a single string. This causes
|
||||
the `ls` command to try to read the contents of a single directory named
|
||||
"my-file; rm -rf /".
|
||||
|
||||
### Security considerations
|
||||
|
||||
While command injection is prevented by default, developers are still
|
||||
responsible for security in certain scenarios.
|
||||
|
||||
Similar to the `Bun.spawn` or `node:child_process.exec()` APIs, you can intentionally
|
||||
execute a command which spawns a new shell (e.g. `bash -c`) with arguments.
|
||||
|
||||
When you do this, you hand off control, and Bun's built-in protections no
|
||||
longer apply to the string interpreted by that new shell.
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const userInput = "world; touch /tmp/pwned";
|
||||
|
||||
// UNSAFE: You have explicitly started a new shell process with `bash -c`.
|
||||
// This new shell will execute the `touch` command. Any user input
|
||||
// passed this way must be rigorously sanitized.
|
||||
await $`bash -c "echo ${userInput}"`;
|
||||
```
|
||||
|
||||
### Argument injection
|
||||
|
||||
The Bun shell cannot know how an external command interprets its own
|
||||
command-line arguments. An attacker can supply input that the target program
|
||||
recognizes as one of its own options or flags, leading to unintended behavior.
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
// Malicious input formatted as a Git command-line flag
|
||||
const branch = "--upload-pack=echo pwned";
|
||||
|
||||
// UNSAFE: While Bun safely passes the string as a single argument,
|
||||
// the `git` program itself sees and acts upon the malicious flag.
|
||||
await $`git ls-remote origin ${branch}`;
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
**Recommendation** — As is best practice in every language, always sanitize
|
||||
user-provided input before passing it as an argument to an external command.
|
||||
The responsibility for validating arguments rests with your application code.
|
||||
{% /callout %}
|
||||
|
||||
## Credits
|
||||
|
||||
Large parts of this API were inspired by [zx](https://github.com/google/zx), [dax](https://github.com/dsherret/dax), and [bnx](https://github.com/wobsoriano/bnx). Thank you to the authors of those projects.
|
||||
|
||||
@@ -1,724 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* CLI Flag Parser for Bun Commands
|
||||
*
|
||||
* This script reads the --help menu for every Bun command and generates JSON
|
||||
* containing all flag information, descriptions, and whether they support
|
||||
* positional or non-positional arguments.
|
||||
*
|
||||
* Handles complex cases like:
|
||||
* - Nested subcommands (bun pm cache rm)
|
||||
* - Command aliases (bun i = bun install, bun a = bun add)
|
||||
* - Dynamic completions (scripts, packages, files)
|
||||
* - Context-aware flags
|
||||
* - Special cases like bare 'bun' vs 'bun run'
|
||||
*
|
||||
* Output is saved to completions/bun-cli.json for use in generating
|
||||
* shell completions (fish, bash, zsh).
|
||||
*/
|
||||
|
||||
import { spawn } from "bun";
|
||||
import { mkdirSync, writeFileSync, mkdtempSync, rmSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
interface FlagInfo {
|
||||
name: string;
|
||||
shortName?: string;
|
||||
description: string;
|
||||
hasValue: boolean;
|
||||
valueType?: string;
|
||||
defaultValue?: string;
|
||||
choices?: string[];
|
||||
required?: boolean;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
interface SubcommandInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
flags?: FlagInfo[];
|
||||
subcommands?: Record<string, SubcommandInfo>;
|
||||
positionalArgs?: {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[];
|
||||
examples?: string[];
|
||||
}
|
||||
|
||||
interface CommandInfo {
|
||||
name: string;
|
||||
aliases?: string[];
|
||||
description: string;
|
||||
usage?: string;
|
||||
flags: FlagInfo[];
|
||||
positionalArgs: {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[];
|
||||
examples: string[];
|
||||
subcommands?: Record<string, SubcommandInfo>;
|
||||
documentationUrl?: string;
|
||||
dynamicCompletions?: {
|
||||
scripts?: boolean;
|
||||
packages?: boolean;
|
||||
files?: boolean;
|
||||
binaries?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface CompletionData {
|
||||
version: string;
|
||||
commands: Record<string, CommandInfo>;
|
||||
globalFlags: FlagInfo[];
|
||||
specialHandling: {
|
||||
bareCommand: {
|
||||
description: string;
|
||||
canRunFiles: boolean;
|
||||
dynamicCompletions: {
|
||||
scripts: boolean;
|
||||
files: boolean;
|
||||
binaries: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
bunGetCompletes: {
|
||||
available: boolean;
|
||||
commands: {
|
||||
scripts: string; // "bun getcompletes s" or "bun getcompletes z"
|
||||
binaries: string; // "bun getcompletes b"
|
||||
packages: string; // "bun getcompletes a <prefix>"
|
||||
files: string; // "bun getcompletes j"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const BUN_EXECUTABLE = process.env.BUN_DEBUG_BUILD || "bun";
|
||||
|
||||
/**
|
||||
* Parse flag line from help output
|
||||
*/
|
||||
function parseFlag(line: string): FlagInfo | null {
|
||||
// Match patterns like:
|
||||
// -h, --help Display this menu and exit
|
||||
// --timeout=<val> Set the per-test timeout in milliseconds, default is 5000.
|
||||
// -r, --preload=<val> Import a module before other modules are loaded
|
||||
// --watch Automatically restart the process on file change
|
||||
|
||||
const patterns = [
|
||||
// Long flag with short flag and value: -r, --preload=<val>
|
||||
/^\s*(-[a-zA-Z]),\s+(--[a-zA-Z-]+)=(<[^>]+>)\s+(.+)$/,
|
||||
// Long flag with short flag: -h, --help
|
||||
/^\s*(-[a-zA-Z]),\s+(--[a-zA-Z-]+)\s+(.+)$/,
|
||||
// Long flag with value: --timeout=<val>
|
||||
/^\s+(--[a-zA-Z-]+)=(<[^>]+>)\s+(.+)$/,
|
||||
// Long flag without value: --watch
|
||||
/^\s+(--[a-zA-Z-]+)\s+(.+)$/,
|
||||
// Short flag only: -i
|
||||
/^\s+(-[a-zA-Z])\s+(.+)$/,
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = line.match(pattern);
|
||||
if (match) {
|
||||
let shortName: string | undefined;
|
||||
let longName: string;
|
||||
let valueSpec: string | undefined;
|
||||
let description: string;
|
||||
|
||||
if (match.length === 5) {
|
||||
// Pattern with short flag, long flag, and value
|
||||
[, shortName, longName, valueSpec, description] = match;
|
||||
} else if (match.length === 4) {
|
||||
if (match[1].startsWith("-") && match[1].length === 2) {
|
||||
// Short flag with long flag
|
||||
[, shortName, longName, description] = match;
|
||||
} else if (match[2].startsWith("<")) {
|
||||
// Long flag with value
|
||||
[, longName, valueSpec, description] = match;
|
||||
} else {
|
||||
// Long flag without value
|
||||
[, longName, description] = match;
|
||||
}
|
||||
} else if (match.length === 3) {
|
||||
if (match[1].length === 2) {
|
||||
// Short flag only
|
||||
[, shortName, description] = match;
|
||||
longName = shortName.replace("-", "--");
|
||||
} else {
|
||||
// Long flag without value
|
||||
[, longName, description] = match;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract additional info from description
|
||||
const hasValue = !!valueSpec;
|
||||
let valueType: string | undefined;
|
||||
let defaultValue: string | undefined;
|
||||
let choices: string[] | undefined;
|
||||
|
||||
if (valueSpec) {
|
||||
valueType = valueSpec.replace(/[<>]/g, "");
|
||||
}
|
||||
|
||||
// Look for default values in description
|
||||
const defaultMatch = description.match(/[Dd]efault(?:s?)\s*(?:is|to|:)\s*"?([^".\s,]+)"?/);
|
||||
if (defaultMatch) {
|
||||
defaultValue = defaultMatch[1];
|
||||
}
|
||||
|
||||
// Look for choices/enums
|
||||
const choicesMatch = description.match(/(?:One of|Valid (?:orders?|values?|options?)):?\s*"?([^"]+)"?/);
|
||||
if (choicesMatch) {
|
||||
choices = choicesMatch[1]
|
||||
.split(/[,\s]+/)
|
||||
.map(s => s.replace(/[",]/g, "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
return {
|
||||
name: longName.replace(/^--/, ""),
|
||||
shortName: shortName?.replace(/^-/, ""),
|
||||
description: description.trim(),
|
||||
hasValue,
|
||||
valueType,
|
||||
defaultValue,
|
||||
choices,
|
||||
required: false, // We'll determine this from usage patterns
|
||||
multiple: description.toLowerCase().includes("multiple") || description.includes("[]"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse usage line to extract positional arguments
|
||||
*/
|
||||
function parseUsage(usage: string): {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[] {
|
||||
const args: {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[] = [];
|
||||
|
||||
// Extract parts after command name
|
||||
const parts = usage.split(/\s+/).slice(2); // Skip "Usage:" and command name
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.startsWith("[") || part.startsWith("<") || part.includes("...")) {
|
||||
let name = part;
|
||||
let required = false;
|
||||
let multiple = false;
|
||||
let completionType: string | undefined;
|
||||
|
||||
// Clean up the argument name
|
||||
name = name.replace(/[\[\]<>]/g, "");
|
||||
|
||||
if (part.startsWith("<")) {
|
||||
required = true;
|
||||
}
|
||||
|
||||
if (part.includes("...") || name.includes("...")) {
|
||||
multiple = true;
|
||||
name = name.replace(/\.{3}/g, "");
|
||||
}
|
||||
|
||||
// Skip flags
|
||||
if (!name.startsWith("-") && name.length > 0) {
|
||||
// Determine completion type based on argument name
|
||||
if (name.toLowerCase().includes("package")) {
|
||||
completionType = "package";
|
||||
} else if (name.toLowerCase().includes("script")) {
|
||||
completionType = "script";
|
||||
} else if (name.toLowerCase().includes("file") || name.includes(".")) {
|
||||
completionType = "file";
|
||||
}
|
||||
|
||||
args.push({
|
||||
name,
|
||||
required,
|
||||
multiple,
|
||||
type: "string", // Default type
|
||||
completionType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
const temppackagejson = mkdtempSync("package");
|
||||
writeFileSync(
|
||||
join(temppackagejson, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "test",
|
||||
version: "1.0.0",
|
||||
scripts: {},
|
||||
}),
|
||||
);
|
||||
process.once("beforeExit", () => {
|
||||
rmSync(temppackagejson, { recursive: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* Execute bun command and get help output
|
||||
*/
|
||||
async function getHelpOutput(command: string[]): Promise<string> {
|
||||
try {
|
||||
const proc = spawn({
|
||||
cmd: [BUN_EXECUTABLE, ...command, "--help"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
cwd: temppackagejson,
|
||||
});
|
||||
|
||||
const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
||||
|
||||
await proc.exited;
|
||||
|
||||
return stdout || stderr || "";
|
||||
} catch (error) {
|
||||
console.error(`Failed to get help for command: ${command.join(" ")}`, error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse PM subcommands from help output
|
||||
*/
|
||||
function parsePmSubcommands(helpText: string): Record<string, SubcommandInfo> {
|
||||
const lines = helpText.split("\n");
|
||||
const subcommands: Record<string, SubcommandInfo> = {};
|
||||
|
||||
let inCommands = false;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === "Commands:") {
|
||||
inCommands = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCommands && trimmed.startsWith("Learn more")) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inCommands && line.match(/^\s+bun pm \w+/)) {
|
||||
// Parse lines like: "bun pm pack create a tarball of the current workspace"
|
||||
const match = line.match(/^\s+bun pm (\S+)(?:\s+(.+))?$/);
|
||||
if (match) {
|
||||
const [, name, description = ""] = match;
|
||||
subcommands[name] = {
|
||||
name,
|
||||
description: description.trim(),
|
||||
flags: [],
|
||||
positionalArgs: [],
|
||||
};
|
||||
|
||||
// Special handling for subcommands with their own subcommands
|
||||
if (name === "cache") {
|
||||
subcommands[name].subcommands = {
|
||||
rm: {
|
||||
name: "rm",
|
||||
description: "clear the cache",
|
||||
},
|
||||
};
|
||||
} else if (name === "pkg") {
|
||||
subcommands[name].subcommands = {
|
||||
get: { name: "get", description: "get values from package.json" },
|
||||
set: { name: "set", description: "set values in package.json" },
|
||||
delete: { name: "delete", description: "delete keys from package.json" },
|
||||
fix: { name: "fix", description: "auto-correct common package.json errors" },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse help output into CommandInfo
|
||||
*/
|
||||
function parseHelpOutput(helpText: string, commandName: string): CommandInfo {
|
||||
const lines = helpText.split("\n");
|
||||
const command: CommandInfo = {
|
||||
name: commandName,
|
||||
description: "",
|
||||
flags: [],
|
||||
positionalArgs: [],
|
||||
examples: [],
|
||||
};
|
||||
|
||||
let currentSection = "";
|
||||
let inFlags = false;
|
||||
let inExamples = false;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Extract command description (usually the first non-usage line)
|
||||
if (
|
||||
!command.description &&
|
||||
trimmed &&
|
||||
!trimmed.startsWith("Usage:") &&
|
||||
!trimmed.startsWith("Alias:") &&
|
||||
currentSection === ""
|
||||
) {
|
||||
command.description = trimmed;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract aliases
|
||||
if (trimmed.startsWith("Alias:")) {
|
||||
const aliasMatch = trimmed.match(/Alias:\s*(.+)/);
|
||||
if (aliasMatch) {
|
||||
command.aliases = aliasMatch[1]
|
||||
.split(/[,\s]+/)
|
||||
.map(a => a.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract usage and positional args
|
||||
if (trimmed.startsWith("Usage:")) {
|
||||
command.usage = trimmed;
|
||||
command.positionalArgs = parseUsage(trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track sections
|
||||
if (trimmed === "Flags:") {
|
||||
inFlags = true;
|
||||
currentSection = "flags";
|
||||
continue;
|
||||
} else if (trimmed === "Examples:") {
|
||||
inExamples = true;
|
||||
inFlags = false;
|
||||
currentSection = "examples";
|
||||
continue;
|
||||
} else if (
|
||||
trimmed.startsWith("Full documentation") ||
|
||||
trimmed.startsWith("Learn more") ||
|
||||
trimmed.startsWith("A full list")
|
||||
) {
|
||||
const urlMatch = trimmed.match(/https?:\/\/[^\s]+/);
|
||||
if (urlMatch) {
|
||||
command.documentationUrl = urlMatch[0];
|
||||
}
|
||||
inFlags = false;
|
||||
inExamples = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse flags
|
||||
if (inFlags && line.match(/^\s+(-|\s+--)/)) {
|
||||
const flag = parseFlag(line);
|
||||
if (flag) {
|
||||
command.flags.push(flag);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse examples
|
||||
if (inExamples && trimmed && !trimmed.startsWith("Full documentation")) {
|
||||
if (trimmed.startsWith("bun ") || trimmed.startsWith("./") || trimmed.startsWith("Bundle")) {
|
||||
command.examples.push(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for pm command
|
||||
if (commandName === "pm") {
|
||||
command.subcommands = parsePmSubcommands(helpText);
|
||||
}
|
||||
|
||||
// Add dynamic completion info based on command
|
||||
command.dynamicCompletions = {};
|
||||
if (commandName === "run") {
|
||||
command.dynamicCompletions.scripts = true;
|
||||
command.dynamicCompletions.files = true;
|
||||
command.dynamicCompletions.binaries = true;
|
||||
// Also add file type info for positional args
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("file") || arg.name.includes("script")) {
|
||||
arg.completionType = "javascript_files";
|
||||
}
|
||||
}
|
||||
} else if (commandName === "add") {
|
||||
command.dynamicCompletions.packages = true;
|
||||
// Mark package args
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("package") || arg.name === "name") {
|
||||
arg.completionType = "package";
|
||||
}
|
||||
}
|
||||
} else if (commandName === "remove") {
|
||||
command.dynamicCompletions.packages = true; // installed packages
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("package") || arg.name === "name") {
|
||||
arg.completionType = "installed_package";
|
||||
}
|
||||
}
|
||||
} else if (["test"].includes(commandName)) {
|
||||
command.dynamicCompletions.files = true;
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("pattern") || arg.name.includes("file")) {
|
||||
arg.completionType = "test_files";
|
||||
}
|
||||
}
|
||||
} else if (["build"].includes(commandName)) {
|
||||
command.dynamicCompletions.files = true;
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name === "entrypoint" || arg.name.includes("file")) {
|
||||
arg.completionType = "javascript_files";
|
||||
}
|
||||
}
|
||||
} else if (commandName === "create") {
|
||||
// Create has special template completions
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("template")) {
|
||||
arg.completionType = "create_template";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of main commands from bun --help
|
||||
*/
|
||||
async function getMainCommands(): Promise<string[]> {
|
||||
const helpText = await getHelpOutput([]);
|
||||
const lines = helpText.split("\n");
|
||||
const commands: string[] = [];
|
||||
|
||||
let inCommands = false;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === "Commands:") {
|
||||
inCommands = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop when we hit the "Flags:" section
|
||||
if (inCommands && trimmed === "Flags:") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inCommands && line.match(/^\s+\w+/)) {
|
||||
// Extract command name (first word after whitespace)
|
||||
const match = line.match(/^\s+(\w+)/);
|
||||
if (match) {
|
||||
commands.push(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const commandsToRemove = ["lint"];
|
||||
|
||||
return commands.filter(a => {
|
||||
if (commandsToRemove.includes(a)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract global flags from main help
|
||||
*/
|
||||
function parseGlobalFlags(helpText: string): FlagInfo[] {
|
||||
const lines = helpText.split("\n");
|
||||
const flags: FlagInfo[] = [];
|
||||
|
||||
let inFlags = false;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === "Flags:") {
|
||||
inFlags = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFlags && (trimmed === "" || trimmed.startsWith("("))) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inFlags && line.match(/^\s+(-|\s+--)/)) {
|
||||
const flag = parseFlag(line);
|
||||
if (flag) {
|
||||
flags.push(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add command aliases based on common patterns
|
||||
*/
|
||||
function addCommandAliases(commands: Record<string, CommandInfo>): void {
|
||||
const aliasMap: Record<string, string[]> = {
|
||||
"install": ["i"],
|
||||
"add": ["a"],
|
||||
"remove": ["rm"],
|
||||
"create": ["c"],
|
||||
"x": ["bunx"], // bunx is an alias for bun x
|
||||
};
|
||||
|
||||
for (const [command, aliases] of Object.entries(aliasMap)) {
|
||||
if (commands[command]) {
|
||||
commands[command].aliases = aliases;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to generate completion data
|
||||
*/
|
||||
async function generateCompletions(): Promise<void> {
|
||||
console.log("🔍 Discovering Bun commands...");
|
||||
|
||||
// Get main help and extract commands
|
||||
const mainHelpText = await getHelpOutput([]);
|
||||
const mainCommands = await getMainCommands();
|
||||
const globalFlags = parseGlobalFlags(mainHelpText);
|
||||
|
||||
console.log(`📋 Found ${mainCommands.length} main commands: ${mainCommands.join(", ")}`);
|
||||
|
||||
const completionData: CompletionData = {
|
||||
version: "1.1.0",
|
||||
commands: {},
|
||||
globalFlags,
|
||||
specialHandling: {
|
||||
bareCommand: {
|
||||
description: "Run JavaScript/TypeScript files directly or access package scripts and binaries",
|
||||
canRunFiles: true,
|
||||
dynamicCompletions: {
|
||||
scripts: true,
|
||||
files: true,
|
||||
binaries: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
bunGetCompletes: {
|
||||
available: true,
|
||||
commands: {
|
||||
scripts: "bun getcompletes s", // or "bun getcompletes z" for scripts with descriptions
|
||||
binaries: "bun getcompletes b",
|
||||
packages: "bun getcompletes a", // takes prefix as argument
|
||||
files: "bun getcompletes j", // JavaScript/TypeScript files
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Parse each command
|
||||
for (const commandName of mainCommands) {
|
||||
console.log(`📖 Parsing help for: ${commandName}`);
|
||||
|
||||
try {
|
||||
const helpText = await getHelpOutput([commandName]);
|
||||
if (helpText.trim()) {
|
||||
const commandInfo = parseHelpOutput(helpText, commandName);
|
||||
completionData.commands[commandName] = commandInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to parse ${commandName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Add common aliases
|
||||
addCommandAliases(completionData.commands);
|
||||
|
||||
// Also check some common subcommands that might have their own help
|
||||
const additionalCommands = ["pm"];
|
||||
for (const commandName of additionalCommands) {
|
||||
if (!completionData.commands[commandName]) {
|
||||
console.log(`📖 Parsing help for additional command: ${commandName}`);
|
||||
|
||||
try {
|
||||
const helpText = await getHelpOutput([commandName]);
|
||||
if (helpText.trim() && !helpText.includes("error:") && !helpText.includes("Error:")) {
|
||||
const commandInfo = parseHelpOutput(helpText, commandName);
|
||||
completionData.commands[commandName] = commandInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to parse ${commandName}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure completions directory exists
|
||||
const completionsDir = join(process.cwd(), "completions");
|
||||
try {
|
||||
mkdirSync(completionsDir, { recursive: true });
|
||||
} catch (error) {
|
||||
// Directory might already exist
|
||||
}
|
||||
|
||||
// Write the JSON file
|
||||
const outputPath = join(completionsDir, "bun-cli.json");
|
||||
const jsonData = JSON.stringify(completionData, null, 2);
|
||||
|
||||
writeFileSync(outputPath, jsonData, "utf8");
|
||||
|
||||
console.log(`✅ Generated CLI completion data at: ${outputPath}`);
|
||||
console.log(`📊 Statistics:`);
|
||||
console.log(` - Commands: ${Object.keys(completionData.commands).length}`);
|
||||
console.log(` - Global flags: ${completionData.globalFlags.length}`);
|
||||
|
||||
let totalFlags = 0;
|
||||
let totalExamples = 0;
|
||||
let totalSubcommands = 0;
|
||||
for (const [name, cmd] of Object.entries(completionData.commands)) {
|
||||
totalFlags += cmd.flags.length;
|
||||
totalExamples += cmd.examples.length;
|
||||
const subcommandCount = cmd.subcommands ? Object.keys(cmd.subcommands).length : 0;
|
||||
totalSubcommands += subcommandCount;
|
||||
|
||||
const aliasInfo = cmd.aliases ? ` (aliases: ${cmd.aliases.join(", ")})` : "";
|
||||
const subcommandInfo = subcommandCount > 0 ? `, ${subcommandCount} subcommands` : "";
|
||||
const dynamicInfo = cmd.dynamicCompletions ? ` [dynamic: ${Object.keys(cmd.dynamicCompletions).join(", ")}]` : "";
|
||||
|
||||
console.log(
|
||||
` - ${name}${aliasInfo}: ${cmd.flags.length} flags, ${cmd.positionalArgs.length} positional args, ${cmd.examples.length} examples${subcommandInfo}${dynamicInfo}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(` - Total command flags: ${totalFlags}`);
|
||||
console.log(` - Total examples: ${totalExamples}`);
|
||||
console.log(` - Total subcommands: ${totalSubcommands}`);
|
||||
}
|
||||
|
||||
// Run the script
|
||||
if (import.meta.main) {
|
||||
generateCompletions().catch(console.error);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
# LLDB Pretty Printers for Bun
|
||||
|
||||
This directory contains LLDB pretty printers for various Bun data structures to improve the debugging experience.
|
||||
|
||||
## Files
|
||||
|
||||
- `bun_pretty_printer.py` - Pretty printers for Bun-specific types (bun.String, WTFStringImpl, ZigString, BabyList, etc.)
|
||||
- `lldb_pretty_printers.py` - Pretty printers for Zig language types from the Zig project
|
||||
- `lldb_webkit.py` - Pretty printers for WebKit/JavaScriptCore types
|
||||
- `init.lldb` - LLDB initialization commands
|
||||
|
||||
## Supported Types
|
||||
|
||||
### bun.String Types
|
||||
- `bun.String` (or just `String`) - The main Bun string type
|
||||
- `WTFStringImpl` - WebKit string implementation (Latin1/UTF16)
|
||||
- `ZigString` - Zig string type (UTF8/Latin1/UTF16 with pointer tagging)
|
||||
|
||||
### Display Format
|
||||
|
||||
The pretty printers show string content directly, with additional metadata:
|
||||
|
||||
```
|
||||
# bun.String examples:
|
||||
"Hello, World!" [latin1] # Regular ZigString
|
||||
"UTF-8 String 🎉" [utf8] # UTF-8 encoded
|
||||
"Static content" [latin1 static] # Static string
|
||||
"" # Empty string
|
||||
<dead> # Dead/invalid string
|
||||
|
||||
# WTFStringImpl examples:
|
||||
"WebKit String" # Shows the actual string content
|
||||
|
||||
# ZigString examples:
|
||||
"Some text" [utf16 global] # UTF16 globally allocated
|
||||
"ASCII text" [latin1] # Latin1 encoded
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Option 1: Manual Loading
|
||||
In your LLDB session:
|
||||
```lldb
|
||||
command script import /path/to/bun/misctools/lldb/bun_pretty_printer.py
|
||||
```
|
||||
|
||||
### Option 2: Add to ~/.lldbinit
|
||||
Add the following line to your `~/.lldbinit` file to load automatically:
|
||||
```lldb
|
||||
command script import /path/to/bun/misctools/lldb/bun_pretty_printer.py
|
||||
```
|
||||
|
||||
### Option 3: Use init.lldb
|
||||
```lldb
|
||||
command source /path/to/bun/misctools/lldb/init.lldb
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To test the pretty printers:
|
||||
|
||||
1. Build a debug version of Bun:
|
||||
```bash
|
||||
bun bd
|
||||
```
|
||||
|
||||
2. Create a test file that uses bun.String types
|
||||
|
||||
3. Debug with LLDB:
|
||||
```bash
|
||||
lldb ./build/debug/bun-debug
|
||||
(lldb) command script import misctools/lldb/bun_pretty_printer.py
|
||||
(lldb) breakpoint set --file your_test.zig --line <line_number>
|
||||
(lldb) run your_test.zig
|
||||
(lldb) frame variable
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### ZigString Pointer Tagging
|
||||
ZigString uses pointer tagging in the upper bits:
|
||||
- Bit 63: 1 = UTF16, 0 = UTF8/Latin1
|
||||
- Bit 62: 1 = Globally allocated (mimalloc)
|
||||
- Bit 61: 1 = UTF8 encoding
|
||||
|
||||
The pretty printer automatically detects and handles these tags.
|
||||
|
||||
### WTFStringImpl Encoding
|
||||
WTFStringImpl uses flags in `m_hashAndFlags`:
|
||||
- Bit 2 (s_hashFlag8BitBuffer): 1 = Latin1, 0 = UTF16
|
||||
|
||||
### bun.String Tag Union
|
||||
bun.String is a tagged union with these variants:
|
||||
- Dead (0): Invalid/freed string
|
||||
- WTFStringImpl (1): WebKit string
|
||||
- ZigString (2): Regular Zig string
|
||||
- StaticZigString (3): Static/immortal string
|
||||
- Empty (4): Empty string ""
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the pretty printers don't work:
|
||||
|
||||
1. Verify the Python script loaded:
|
||||
```lldb
|
||||
(lldb) script print("Python works")
|
||||
```
|
||||
|
||||
2. Check if the category is enabled:
|
||||
```lldb
|
||||
(lldb) type category list
|
||||
```
|
||||
|
||||
3. Enable the Bun category manually:
|
||||
```lldb
|
||||
(lldb) type category enable bun
|
||||
```
|
||||
|
||||
4. For debugging the pretty printer itself, check for exceptions:
|
||||
- The pretty printers catch all exceptions and return `<error>`
|
||||
- Modify the code to print exceptions for debugging
|
||||
@@ -10,8 +10,8 @@ class bun_BabyList_SynthProvider:
|
||||
|
||||
try:
|
||||
self.ptr = self.value.GetChildMemberWithName('ptr')
|
||||
self.len = self.value.GetChildMemberWithName('len').GetValueAsUnsigned()
|
||||
self.cap = self.value.GetChildMemberWithName('cap').GetValueAsUnsigned()
|
||||
self.len = self.value.GetChildMemberWithName('len').unsigned
|
||||
self.cap = self.value.GetChildMemberWithName('cap').unsigned
|
||||
self.elem_type = self.ptr.type.GetPointeeType()
|
||||
self.elem_size = self.elem_type.size
|
||||
except:
|
||||
@@ -46,7 +46,7 @@ def bun_BabyList_SummaryProvider(value, _=None):
|
||||
value = value.GetNonSyntheticValue()
|
||||
len_val = value.GetChildMemberWithName('len')
|
||||
cap_val = value.GetChildMemberWithName('cap')
|
||||
return 'len=%d cap=%d' % (len_val.GetValueAsUnsigned(), cap_val.GetValueAsUnsigned())
|
||||
return 'len=%d cap=%d' % (len_val.unsigned, cap_val.unsigned)
|
||||
except:
|
||||
return 'len=? cap=?'
|
||||
|
||||
@@ -67,241 +67,6 @@ def add(debugger, *, category, regex=False, type, identifier=None, synth=False,
|
||||
type
|
||||
))
|
||||
|
||||
def WTFStringImpl_SummaryProvider(value, _=None):
|
||||
try:
|
||||
# Get the raw pointer (it's already a pointer type)
|
||||
value = value.GetNonSyntheticValue()
|
||||
|
||||
# Check if it's a pointer type and dereference if needed
|
||||
if value.type.IsPointerType():
|
||||
struct = value.deref
|
||||
else:
|
||||
struct = value
|
||||
|
||||
m_length = struct.GetChildMemberWithName('m_length').GetValueAsUnsigned()
|
||||
m_hashAndFlags = struct.GetChildMemberWithName('m_hashAndFlags').GetValueAsUnsigned()
|
||||
m_ptr = struct.GetChildMemberWithName('m_ptr')
|
||||
|
||||
# Check if it's 8-bit (latin1) or 16-bit (utf16) string
|
||||
s_hashFlag8BitBuffer = 1 << 2
|
||||
is_8bit = (m_hashAndFlags & s_hashFlag8BitBuffer) != 0
|
||||
|
||||
if m_length == 0:
|
||||
return '[%s] ""' % ('latin1' if is_8bit else 'utf16')
|
||||
|
||||
# Limit memory reads to 1MB for performance
|
||||
MAX_BYTES = 1024 * 1024 # 1MB
|
||||
MAX_DISPLAY_CHARS = 200 # Maximum characters to display
|
||||
|
||||
# Calculate how much to read
|
||||
bytes_per_char = 1 if is_8bit else 2
|
||||
total_bytes = m_length * bytes_per_char
|
||||
truncated = False
|
||||
|
||||
if total_bytes > MAX_BYTES:
|
||||
# Read only first part of very large strings
|
||||
chars_to_read = MAX_BYTES // bytes_per_char
|
||||
bytes_to_read = chars_to_read * bytes_per_char
|
||||
truncated = True
|
||||
else:
|
||||
chars_to_read = m_length
|
||||
bytes_to_read = total_bytes
|
||||
|
||||
if is_8bit:
|
||||
# Latin1 string
|
||||
latin1_ptr = m_ptr.GetChildMemberWithName('latin1')
|
||||
process = value.process
|
||||
error = lldb.SBError()
|
||||
ptr_addr = latin1_ptr.GetValueAsUnsigned()
|
||||
if ptr_addr:
|
||||
byte_data = process.ReadMemory(ptr_addr, min(chars_to_read, m_length), error)
|
||||
if error.Success():
|
||||
string_val = byte_data.decode('latin1', errors='replace')
|
||||
else:
|
||||
return '[latin1] <read error: %s>' % error
|
||||
else:
|
||||
return '[latin1] <null ptr>'
|
||||
else:
|
||||
# UTF16 string
|
||||
utf16_ptr = m_ptr.GetChildMemberWithName('utf16')
|
||||
process = value.process
|
||||
error = lldb.SBError()
|
||||
ptr_addr = utf16_ptr.GetValueAsUnsigned()
|
||||
if ptr_addr:
|
||||
byte_data = process.ReadMemory(ptr_addr, bytes_to_read, error)
|
||||
if error.Success():
|
||||
# Properly decode UTF16LE to string
|
||||
string_val = byte_data.decode('utf-16le', errors='replace')
|
||||
else:
|
||||
return '[utf16] <read error: %s>' % error
|
||||
else:
|
||||
return '[utf16] <null ptr>'
|
||||
|
||||
# Escape special characters
|
||||
string_val = string_val.replace('\\', '\\\\')
|
||||
string_val = string_val.replace('"', '\\"')
|
||||
string_val = string_val.replace('\n', '\\n')
|
||||
string_val = string_val.replace('\r', '\\r')
|
||||
string_val = string_val.replace('\t', '\\t')
|
||||
|
||||
# Truncate display if too long
|
||||
display_truncated = truncated or len(string_val) > MAX_DISPLAY_CHARS
|
||||
if len(string_val) > MAX_DISPLAY_CHARS:
|
||||
string_val = string_val[:MAX_DISPLAY_CHARS]
|
||||
|
||||
# Add encoding and size info at the beginning
|
||||
encoding = 'latin1' if is_8bit else 'utf16'
|
||||
|
||||
if display_truncated:
|
||||
size_info = ' %d chars' % m_length
|
||||
if total_bytes >= 1024 * 1024:
|
||||
size_info += ' (%.1fMB)' % (total_bytes / (1024.0 * 1024.0))
|
||||
elif total_bytes >= 1024:
|
||||
size_info += ' (%.1fKB)' % (total_bytes / 1024.0)
|
||||
return '[%s%s] "%s..." <truncated>' % (encoding, size_info, string_val)
|
||||
else:
|
||||
return '[%s] "%s"' % (encoding, string_val)
|
||||
except:
|
||||
return '<error>'
|
||||
|
||||
def ZigString_SummaryProvider(value, _=None):
|
||||
try:
|
||||
value = value.GetNonSyntheticValue()
|
||||
|
||||
ptr = value.GetChildMemberWithName('_unsafe_ptr_do_not_use').GetValueAsUnsigned()
|
||||
length = value.GetChildMemberWithName('len').GetValueAsUnsigned()
|
||||
|
||||
# Check encoding flags
|
||||
is_16bit = (ptr & (1 << 63)) != 0
|
||||
is_utf8 = (ptr & (1 << 61)) != 0
|
||||
is_global = (ptr & (1 << 62)) != 0
|
||||
|
||||
# Determine encoding
|
||||
encoding = 'utf16' if is_16bit else ('utf8' if is_utf8 else 'latin1')
|
||||
flags = ' global' if is_global else ''
|
||||
|
||||
if length == 0:
|
||||
return '[%s%s] ""' % (encoding, flags)
|
||||
|
||||
# Untag the pointer (keep only the lower 53 bits)
|
||||
untagged_ptr = ptr & ((1 << 53) - 1)
|
||||
|
||||
# Limit memory reads to 1MB for performance
|
||||
MAX_BYTES = 1024 * 1024 # 1MB
|
||||
MAX_DISPLAY_CHARS = 200 # Maximum characters to display
|
||||
|
||||
# Calculate how much to read
|
||||
bytes_per_char = 2 if is_16bit else 1
|
||||
total_bytes = length * bytes_per_char
|
||||
truncated = False
|
||||
|
||||
if total_bytes > MAX_BYTES:
|
||||
# Read only first part of very large strings
|
||||
chars_to_read = MAX_BYTES // bytes_per_char
|
||||
bytes_to_read = chars_to_read * bytes_per_char
|
||||
truncated = True
|
||||
else:
|
||||
bytes_to_read = total_bytes
|
||||
|
||||
# Read the string data
|
||||
process = value.process
|
||||
error = lldb.SBError()
|
||||
|
||||
byte_data = process.ReadMemory(untagged_ptr, bytes_to_read, error)
|
||||
if not error.Success():
|
||||
return '[%s%s] <read error>' % (encoding, flags)
|
||||
|
||||
# Decode based on encoding
|
||||
if is_16bit:
|
||||
string_val = byte_data.decode('utf-16le', errors='replace')
|
||||
elif is_utf8:
|
||||
string_val = byte_data.decode('utf-8', errors='replace')
|
||||
else:
|
||||
string_val = byte_data.decode('latin1', errors='replace')
|
||||
|
||||
# Escape special characters
|
||||
string_val = string_val.replace('\\', '\\\\')
|
||||
string_val = string_val.replace('"', '\\"')
|
||||
string_val = string_val.replace('\n', '\\n')
|
||||
string_val = string_val.replace('\r', '\\r')
|
||||
string_val = string_val.replace('\t', '\\t')
|
||||
|
||||
# Truncate display if too long
|
||||
display_truncated = truncated or len(string_val) > MAX_DISPLAY_CHARS
|
||||
if len(string_val) > MAX_DISPLAY_CHARS:
|
||||
string_val = string_val[:MAX_DISPLAY_CHARS]
|
||||
|
||||
# Build the output
|
||||
if display_truncated:
|
||||
size_info = ' %d chars' % length
|
||||
if total_bytes >= 1024 * 1024:
|
||||
size_info += ' (%.1fMB)' % (total_bytes / (1024.0 * 1024.0))
|
||||
elif total_bytes >= 1024:
|
||||
size_info += ' (%.1fKB)' % (total_bytes / 1024.0)
|
||||
return '[%s%s%s] "%s..." <truncated>' % (encoding, flags, size_info, string_val)
|
||||
else:
|
||||
return '[%s%s] "%s"' % (encoding, flags, string_val)
|
||||
except:
|
||||
return '<error>'
|
||||
|
||||
def bun_String_SummaryProvider(value, _=None):
|
||||
try:
|
||||
value = value.GetNonSyntheticValue()
|
||||
|
||||
# Debug: Show the actual type name LLDB sees
|
||||
type_name = value.GetTypeName()
|
||||
|
||||
tag = value.GetChildMemberWithName('tag')
|
||||
if not tag or not tag.IsValid():
|
||||
# Try alternate field names
|
||||
tag = value.GetChildMemberWithName('Tag')
|
||||
if not tag or not tag.IsValid():
|
||||
# Show type name to help debug
|
||||
return '<no tag field in type: %s>' % type_name
|
||||
|
||||
tag_value = tag.GetValueAsUnsigned()
|
||||
|
||||
# Map tag values to names
|
||||
tag_names = {
|
||||
0: 'Dead',
|
||||
1: 'WTFStringImpl',
|
||||
2: 'ZigString',
|
||||
3: 'StaticZigString',
|
||||
4: 'Empty'
|
||||
}
|
||||
|
||||
tag_name = tag_names.get(tag_value, 'Unknown')
|
||||
|
||||
if tag_name == 'Empty':
|
||||
return '""'
|
||||
elif tag_name == 'Dead':
|
||||
return '<dead>'
|
||||
elif tag_name == 'WTFStringImpl':
|
||||
value_union = value.GetChildMemberWithName('value')
|
||||
if not value_union or not value_union.IsValid():
|
||||
return '<no value field>'
|
||||
impl_value = value_union.GetChildMemberWithName('WTFStringImpl')
|
||||
if not impl_value or not impl_value.IsValid():
|
||||
return '<no WTFStringImpl field>'
|
||||
return WTFStringImpl_SummaryProvider(impl_value, _)
|
||||
elif tag_name == 'ZigString' or tag_name == 'StaticZigString':
|
||||
value_union = value.GetChildMemberWithName('value')
|
||||
if not value_union or not value_union.IsValid():
|
||||
return '<no value field>'
|
||||
field_name = 'ZigString' if tag_name == 'ZigString' else 'StaticZigString'
|
||||
zig_string_value = value_union.GetChildMemberWithName(field_name)
|
||||
if not zig_string_value or not zig_string_value.IsValid():
|
||||
return '<no %s field>' % field_name
|
||||
result = ZigString_SummaryProvider(zig_string_value, _)
|
||||
# Add static marker if needed
|
||||
if tag_name == 'StaticZigString':
|
||||
result = result.replace(']', ' static]')
|
||||
return result
|
||||
else:
|
||||
return '<unknown tag %d>' % tag_value
|
||||
except Exception as e:
|
||||
return '<error: %s>' % str(e)
|
||||
|
||||
def __lldb_init_module(debugger, _=None):
|
||||
# Initialize Bun Category
|
||||
debugger.HandleCommand('type category define --language c99 bun')
|
||||
@@ -309,30 +74,5 @@ def __lldb_init_module(debugger, _=None):
|
||||
# Initialize Bun Data Structures
|
||||
add(debugger, category='bun', regex=True, type='^baby_list\\.BabyList\\(.*\\)$', identifier='bun_BabyList', synth=True, expand=True, summary=True)
|
||||
|
||||
# Add WTFStringImpl pretty printer - try multiple possible type names
|
||||
add(debugger, category='bun', type='WTFStringImpl', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='*WTFStringImplStruct', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='string.WTFStringImpl', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='string.WTFStringImplStruct', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='*string.WTFStringImplStruct', identifier='WTFStringImpl', summary=True)
|
||||
|
||||
# Add ZigString pretty printer - try multiple possible type names
|
||||
add(debugger, category='bun', type='ZigString', identifier='ZigString', summary=True)
|
||||
add(debugger, category='bun', type='bun.js.bindings.ZigString', identifier='ZigString', summary=True)
|
||||
add(debugger, category='bun', type='bindings.ZigString', identifier='ZigString', summary=True)
|
||||
|
||||
# Add bun.String pretty printer - try multiple possible type names
|
||||
add(debugger, category='bun', type='String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='bun.String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='string.String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='BunString', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='bun::String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='bun::string::String', identifier='bun_String', summary=True)
|
||||
|
||||
# Try regex patterns for more flexible matching
|
||||
add(debugger, category='bun', regex=True, type='.*String$', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', regex=True, type='.*WTFStringImpl.*', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', regex=True, type='.*ZigString.*', identifier='ZigString', summary=True)
|
||||
|
||||
# Enable the category
|
||||
debugger.HandleCommand('type category enable bun')
|
||||
11
package.json
11
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bun",
|
||||
"version": "1.2.21",
|
||||
"version": "1.2.20",
|
||||
"workspaces": [
|
||||
"./packages/bun-types",
|
||||
"./packages/@types/bun"
|
||||
@@ -48,9 +48,6 @@
|
||||
"css-properties": "bun run src/css/properties/generate_properties.ts",
|
||||
"uv-posix-stubs": "bun run src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts",
|
||||
"bump": "bun ./scripts/bump.ts",
|
||||
"jsc:build": "bun ./scripts/build-jsc.ts release",
|
||||
"jsc:build:debug": "bun ./scripts/build-jsc.ts debug",
|
||||
"jsc:build:lto": "bun ./scripts/build-jsc.ts lto",
|
||||
"typecheck": "tsc --noEmit && cd test && bun run typecheck",
|
||||
"fmt": "bun run prettier",
|
||||
"fmt:cpp": "bun run clang-format",
|
||||
@@ -71,9 +68,9 @@
|
||||
"zig:check-windows": "bun run zig build check-windows --summary new",
|
||||
"analysis": "bun ./scripts/build.mjs -DCMAKE_BUILD_TYPE=Debug -DENABLE_ANALYSIS=ON -DENABLE_CCACHE=OFF -B build/analysis",
|
||||
"analysis:no-llvm": "bun run analysis -DENABLE_LLVM=OFF",
|
||||
"clang-format": "./scripts/run-clang-format.sh format",
|
||||
"clang-format:check": "./scripts/run-clang-format.sh check",
|
||||
"clang-format:diff": "./scripts/run-clang-format.sh diff",
|
||||
"clang-format": "bun run analysis --target clang-format",
|
||||
"clang-format:check": "bun run analysis --target clang-format-check",
|
||||
"clang-format:diff": "bun run analysis --target clang-format-diff",
|
||||
"clang-tidy": "bun run analysis --target clang-tidy",
|
||||
"clang-tidy:check": "bun run analysis --target clang-tidy-check",
|
||||
"clang-tidy:diff": "bun run analysis --target clang-tidy-diff",
|
||||
|
||||
27
packages/bun-types/bun.d.ts
vendored
27
packages/bun-types/bun.d.ts
vendored
@@ -596,23 +596,6 @@ declare module "bun" {
|
||||
options?: StringWidthOptions,
|
||||
): number;
|
||||
|
||||
/**
|
||||
* Remove ANSI escape codes from a string.
|
||||
*
|
||||
* @category Utilities
|
||||
*
|
||||
* @param input The string to remove ANSI escape codes from.
|
||||
* @returns The string with ANSI escape codes removed.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { stripANSI } from "bun";
|
||||
*
|
||||
* console.log(stripANSI("\u001b[31mhello\u001b[39m")); // "hello"
|
||||
* ```
|
||||
*/
|
||||
function stripANSI(input: string): string;
|
||||
|
||||
/**
|
||||
* TOML related APIs
|
||||
*/
|
||||
@@ -7546,11 +7529,10 @@ declare module "bun" {
|
||||
* Internally, this uses [posix_spawn(2)](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/posix_spawn.2.html)
|
||||
*/
|
||||
function spawnSync<
|
||||
const In extends SpawnOptions.Writable = "ignore",
|
||||
const Out extends SpawnOptions.Readable = "pipe",
|
||||
const Err extends SpawnOptions.Readable = "pipe",
|
||||
const Err extends SpawnOptions.Readable = "inherit",
|
||||
>(
|
||||
options: SpawnOptions.OptionsObject<In, Out, Err> & {
|
||||
options: SpawnOptions.OptionsObject<"ignore", Out, Err> & {
|
||||
/**
|
||||
* The command to run
|
||||
*
|
||||
@@ -7582,9 +7564,8 @@ declare module "bun" {
|
||||
* Internally, this uses [posix_spawn(2)](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/posix_spawn.2.html)
|
||||
*/
|
||||
function spawnSync<
|
||||
const In extends SpawnOptions.Writable = "ignore",
|
||||
const Out extends SpawnOptions.Readable = "pipe",
|
||||
const Err extends SpawnOptions.Readable = "pipe",
|
||||
const Err extends SpawnOptions.Readable = "inherit",
|
||||
>(
|
||||
/**
|
||||
* The command to run
|
||||
@@ -7601,7 +7582,7 @@ declare module "bun" {
|
||||
* ```
|
||||
*/
|
||||
cmds: string[],
|
||||
options?: SpawnOptions.OptionsObject<In, Out, Err>,
|
||||
options?: SpawnOptions.OptionsObject<"ignore", Out, Err>,
|
||||
): SyncSubprocess<Out, Err>;
|
||||
|
||||
/** Utility type for any process from {@link Bun.spawn()} with both stdout and stderr set to `"pipe"` */
|
||||
|
||||
19
packages/bun-types/globals.d.ts
vendored
19
packages/bun-types/globals.d.ts
vendored
@@ -1888,25 +1888,6 @@ interface BunFetchRequestInit extends RequestInit {
|
||||
* ```
|
||||
*/
|
||||
unix?: string;
|
||||
|
||||
/**
|
||||
* Control automatic decompression of the response body.
|
||||
* When set to `false`, the response body will not be automatically decompressed,
|
||||
* and the `Content-Encoding` header will be preserved. This can improve performance
|
||||
* when you need to handle compressed data manually or forward it as-is.
|
||||
* This is a custom property that is not part of the Fetch API specification.
|
||||
*
|
||||
* @default true
|
||||
* @example
|
||||
* ```js
|
||||
* // Disable automatic decompression for a proxy server
|
||||
* const response = await fetch("https://example.com/api", {
|
||||
* decompress: false
|
||||
* });
|
||||
* // response.headers.get('content-encoding') might be 'gzip' or 'br'
|
||||
* ```
|
||||
*/
|
||||
decompress?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
44
packages/bun-types/redis.d.ts
vendored
44
packages/bun-types/redis.d.ts
vendored
@@ -574,50 +574,6 @@ declare module "bun" {
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration in seconds
|
||||
* @param key The key to get
|
||||
* @param ex Set the specified expire time, in seconds
|
||||
* @param seconds The number of seconds until expiration
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, ex: "EX", seconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration in milliseconds
|
||||
* @param key The key to get
|
||||
* @param px Set the specified expire time, in milliseconds
|
||||
* @param milliseconds The number of milliseconds until expiration
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, px: "PX", milliseconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration at a specific Unix timestamp in seconds
|
||||
* @param key The key to get
|
||||
* @param exat Set the specified Unix time at which the key will expire, in seconds
|
||||
* @param timestampSeconds The Unix timestamp in seconds
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, exat: "EXAT", timestampSeconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration at a specific Unix timestamp in milliseconds
|
||||
* @param key The key to get
|
||||
* @param pxat Set the specified Unix time at which the key will expire, in milliseconds
|
||||
* @param timestampMilliseconds The Unix timestamp in milliseconds
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, pxat: "PXAT", timestampMilliseconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and remove its expiration
|
||||
* @param key The key to get
|
||||
* @param persist Remove the expiration from the key
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, persist: "PERSIST"): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Ping the server
|
||||
* @returns Promise that resolves with "PONG" if the server is reachable, or throws an error if the server is not reachable
|
||||
|
||||
@@ -346,7 +346,6 @@ us_internal_ssl_socket_close(struct us_internal_ssl_socket_t *s, int code,
|
||||
|
||||
// check if we are already closed
|
||||
if (us_internal_ssl_socket_is_closed(s)) return s;
|
||||
us_internal_set_loop_ssl_data(s);
|
||||
us_internal_update_handshake(s);
|
||||
|
||||
if (s->handshake_state != HANDSHAKE_COMPLETED) {
|
||||
|
||||
@@ -30,17 +30,13 @@ extern void __attribute((__noreturn__)) Bun__panic(const char* message, size_t l
|
||||
#define BUN_PANIC(message) Bun__panic(message, sizeof(message) - 1)
|
||||
#endif
|
||||
|
||||
extern void Bun__internal_ensureDateHeaderTimerIsEnabled(struct us_loop_t *loop);
|
||||
|
||||
void sweep_timer_cb(struct us_internal_callback_t *cb);
|
||||
|
||||
void us_internal_enable_sweep_timer(struct us_loop_t *loop) {
|
||||
loop->data.sweep_timer_count++;
|
||||
if (loop->data.sweep_timer_count == 1) {
|
||||
if (loop->data.sweep_timer_count == 0) {
|
||||
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
|
||||
Bun__internal_ensureDateHeaderTimerIsEnabled(loop);
|
||||
}
|
||||
|
||||
loop->data.sweep_timer_count++;
|
||||
}
|
||||
|
||||
void us_internal_disable_sweep_timer(struct us_loop_t *loop) {
|
||||
|
||||
@@ -222,78 +222,6 @@ namespace uWS
|
||||
return std::string_view(nullptr, 0);
|
||||
}
|
||||
|
||||
struct TransferEncoding {
|
||||
bool has: 1 = false;
|
||||
bool chunked: 1 = false;
|
||||
bool invalid: 1 = false;
|
||||
};
|
||||
|
||||
TransferEncoding getTransferEncoding()
|
||||
{
|
||||
TransferEncoding te;
|
||||
|
||||
if (!bf.mightHave("transfer-encoding")) {
|
||||
return te;
|
||||
}
|
||||
|
||||
for (Header *h = headers; (++h)->key.length();) {
|
||||
if (h->key.length() == 17 && !strncmp(h->key.data(), "transfer-encoding", 17)) {
|
||||
// Parse comma-separated values, ensuring "chunked" is last if present
|
||||
const auto value = h->value;
|
||||
size_t pos = 0;
|
||||
size_t lastTokenStart = 0;
|
||||
size_t lastTokenLen = 0;
|
||||
|
||||
while (pos < value.length()) {
|
||||
// Skip leading whitespace
|
||||
while (pos < value.length() && (value[pos] == ' ' || value[pos] == '\t')) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Remember start of this token
|
||||
size_t tokenStart = pos;
|
||||
|
||||
// Find end of token (until comma or end)
|
||||
while (pos < value.length() && value[pos] != ',') {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Trim trailing whitespace from token
|
||||
size_t tokenEnd = pos;
|
||||
while (tokenEnd > tokenStart && (value[tokenEnd - 1] == ' ' || value[tokenEnd - 1] == '\t')) {
|
||||
tokenEnd--;
|
||||
}
|
||||
|
||||
size_t tokenLen = tokenEnd - tokenStart;
|
||||
if (tokenLen > 0) {
|
||||
lastTokenStart = tokenStart;
|
||||
lastTokenLen = tokenLen;
|
||||
}
|
||||
|
||||
// Move past comma if present
|
||||
if (pos < value.length() && value[pos] == ',') {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (te.chunked) [[unlikely]] {
|
||||
te.invalid = true;
|
||||
return te;
|
||||
}
|
||||
|
||||
te.has = lastTokenLen > 0;
|
||||
|
||||
// Check if the last token is "chunked"
|
||||
if (lastTokenLen == 7 && !strncmp(value.data() + lastTokenStart, "chunked", 7)) [[likely]] {
|
||||
te.chunked = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return te;
|
||||
}
|
||||
|
||||
|
||||
std::string_view getUrl()
|
||||
{
|
||||
@@ -843,16 +771,14 @@ namespace uWS
|
||||
* the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt
|
||||
* to perform request smuggling (Section 11.2) or response splitting (Section 11.1) and
|
||||
* ought to be handled as an error. */
|
||||
const std::string_view contentLengthString = req->getHeader("content-length");
|
||||
const auto contentLengthStringLen = contentLengthString.length();
|
||||
|
||||
/* Check Transfer-Encoding header validity and conflicts */
|
||||
HttpRequest::TransferEncoding transferEncoding = req->getTransferEncoding();
|
||||
std::string_view transferEncodingString = req->getHeader("transfer-encoding");
|
||||
std::string_view contentLengthString = req->getHeader("content-length");
|
||||
|
||||
transferEncoding.invalid = transferEncoding.invalid || (transferEncoding.has && (contentLengthStringLen || !transferEncoding.chunked));
|
||||
|
||||
if (transferEncoding.invalid) [[unlikely]] {
|
||||
/* Invalid Transfer-Encoding (multiple headers or chunked not last - request smuggling attempt) */
|
||||
auto transferEncodingStringLen = transferEncodingString.length();
|
||||
auto contentLengthStringLen = contentLengthString.length();
|
||||
if (transferEncodingStringLen && contentLengthStringLen) {
|
||||
/* We could be smart and set an error in the context along with this, to indicate what
|
||||
* http error response we might want to return */
|
||||
return HttpParserResult::error(HTTP_ERROR_400_BAD_REQUEST, HTTP_PARSER_ERROR_INVALID_TRANSFER_ENCODING);
|
||||
}
|
||||
|
||||
@@ -863,7 +789,7 @@ namespace uWS
|
||||
// lets check if content len is valid before calling requestHandler
|
||||
if(contentLengthStringLen) {
|
||||
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
|
||||
if (remainingStreamingBytes == UINT64_MAX) [[unlikely]] {
|
||||
if (remainingStreamingBytes == UINT64_MAX) {
|
||||
/* Parser error */
|
||||
return HttpParserResult::error(HTTP_ERROR_400_BAD_REQUEST, HTTP_PARSER_ERROR_INVALID_CONTENT_LENGTH);
|
||||
}
|
||||
@@ -887,8 +813,20 @@ namespace uWS
|
||||
/* RFC 9112 6.3
|
||||
* If a message is received with both a Transfer-Encoding and a Content-Length header field,
|
||||
* the Transfer-Encoding overrides the Content-Length. */
|
||||
if (transferEncoding.has) {
|
||||
/* We already validated that chunked is last if present, before calling the handler */
|
||||
if (transferEncodingStringLen) {
|
||||
|
||||
/* If a proxy sent us the transfer-encoding header that 100% means it must be chunked or else the proxy is
|
||||
* not RFC 9112 compliant. Therefore it is always better to assume this is the case, since that entirely eliminates
|
||||
* all forms of transfer-encoding obfuscation tricks. We just rely on the header. */
|
||||
|
||||
/* RFC 9112 6.3
|
||||
* If a Transfer-Encoding header field is present in a request and the chunked transfer coding is not the
|
||||
* final encoding, the message body length cannot be determined reliably; the server MUST respond with the
|
||||
* 400 (Bad Request) status code and then close the connection. */
|
||||
|
||||
/* In this case we fail later by having the wrong interpretation (assuming chunked).
|
||||
* This could be made stricter but makes no difference either way, unless forwarding the identical message as a proxy. */
|
||||
|
||||
remainingStreamingBytes = STATE_IS_CHUNKED;
|
||||
/* If consume minimally, we do not want to consume anything but we want to mark this as being chunked */
|
||||
if constexpr (!ConsumeMinimally) {
|
||||
@@ -897,7 +835,7 @@ namespace uWS
|
||||
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes)) {
|
||||
dataHandler(user, chunk, chunk.length() == 0);
|
||||
}
|
||||
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) [[unlikely]] {
|
||||
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
|
||||
// TODO: what happen if we already responded?
|
||||
return HttpParserResult::error(HTTP_ERROR_400_BAD_REQUEST, HTTP_PARSER_ERROR_INVALID_CHUNKED_ENCODING);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,19 @@ private:
|
||||
|
||||
static Loop *create(void *hint) {
|
||||
Loop *loop = ((Loop *) us_create_loop(hint, wakeupCb, preCb, postCb, sizeof(LoopData)))->init();
|
||||
|
||||
/* We also need some timers (should live off the one 4 second timer rather) */
|
||||
LoopData *loopData = (LoopData *) us_loop_ext((struct us_loop_t *) loop);
|
||||
loopData->dateTimer = us_create_timer((struct us_loop_t *) loop, 1, sizeof(LoopData *));
|
||||
loopData->updateDate();
|
||||
|
||||
memcpy(us_timer_ext(loopData->dateTimer), &loopData, sizeof(LoopData *));
|
||||
us_timer_set(loopData->dateTimer, [](struct us_timer_t *t) {
|
||||
LoopData *loopData;
|
||||
memcpy(&loopData, us_timer_ext(t), sizeof(LoopData *));
|
||||
loopData->updateDate();
|
||||
}, 1000, 1000);
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
@@ -133,7 +146,10 @@ public:
|
||||
/* Freeing the default loop should be done once */
|
||||
void free() {
|
||||
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
|
||||
|
||||
|
||||
/* Stop and free dateTimer first */
|
||||
us_timer_close(loopData->dateTimer, 1);
|
||||
|
||||
loopData->~LoopData();
|
||||
/* uSockets will track whether this loop is owned by us or a borrowed alien loop */
|
||||
us_loop_free((us_loop_t *) this);
|
||||
|
||||
@@ -151,6 +151,8 @@ public:
|
||||
ZlibContext *zlibContext = nullptr;
|
||||
InflationStream *inflationStream = nullptr;
|
||||
DeflationStream *deflationStream = nullptr;
|
||||
|
||||
us_timer_t *dateTimer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -163,11 +163,8 @@ export class BunTestController implements vscode.Disposable {
|
||||
const ignoreGlobs = await this.buildIgnoreGlobs(cancellationToken);
|
||||
const tests = await vscode.workspace.findFiles(
|
||||
this.customFilePattern(),
|
||||
"**/node_modules/**",
|
||||
// 5k tests is more than enough for most projects.
|
||||
// If they need more, they can manually open the files themself and it should be added to the test explorer.
|
||||
// This is needed because otherwise with too many tests, vscode OOMs.
|
||||
5_000,
|
||||
"node_modules",
|
||||
undefined,
|
||||
cancellationToken,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import { spawnSync } from "child_process";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { arch, platform } from "os";
|
||||
import { join, resolve } from "path";
|
||||
|
||||
// Build configurations
|
||||
type BuildConfig = "debug" | "release" | "lto";
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const buildConfig: BuildConfig = (args[0] as BuildConfig) || "debug";
|
||||
const validConfigs = ["debug", "release", "lto"];
|
||||
|
||||
if (!validConfigs.includes(buildConfig)) {
|
||||
console.error(`Invalid build configuration: ${buildConfig}`);
|
||||
console.error(`Valid configurations: ${validConfigs.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Detect platform
|
||||
const OS_NAME = platform().toLowerCase();
|
||||
const ARCH_NAME_RAW = arch();
|
||||
const IS_MAC = OS_NAME === "darwin";
|
||||
const IS_LINUX = OS_NAME === "linux";
|
||||
const IS_ARM64 = ARCH_NAME_RAW === "arm64" || ARCH_NAME_RAW === "aarch64";
|
||||
|
||||
// Paths
|
||||
const ROOT_DIR = resolve(import.meta.dir, "..");
|
||||
const WEBKIT_DIR = resolve(ROOT_DIR, "vendor/WebKit");
|
||||
const WEBKIT_BUILD_DIR = join(WEBKIT_DIR, "WebKitBuild");
|
||||
const WEBKIT_RELEASE_DIR = join(WEBKIT_BUILD_DIR, "Release");
|
||||
const WEBKIT_DEBUG_DIR = join(WEBKIT_BUILD_DIR, "Debug");
|
||||
const WEBKIT_RELEASE_DIR_LTO = join(WEBKIT_BUILD_DIR, "ReleaseLTO");
|
||||
|
||||
// Homebrew prefix detection
|
||||
const HOMEBREW_PREFIX = IS_ARM64 ? "/opt/homebrew/" : "/usr/local/";
|
||||
|
||||
// Compiler detection
|
||||
function findExecutable(names: string[]): string | null {
|
||||
for (const name of names) {
|
||||
const result = spawnSync("which", [name], { encoding: "utf8" });
|
||||
if (result.status === 0) {
|
||||
return result.stdout.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const CC = findExecutable(["clang-19", "clang"]) || "clang";
|
||||
const CXX = findExecutable(["clang++-19", "clang++"]) || "clang++";
|
||||
|
||||
// Build directory based on config
|
||||
const getBuildDir = (config: BuildConfig) => {
|
||||
switch (config) {
|
||||
case "debug":
|
||||
return WEBKIT_DEBUG_DIR;
|
||||
case "lto":
|
||||
return WEBKIT_RELEASE_DIR_LTO;
|
||||
default:
|
||||
return WEBKIT_RELEASE_DIR;
|
||||
}
|
||||
};
|
||||
|
||||
// Common CMake flags
|
||||
const getCommonFlags = () => {
|
||||
const flags = [
|
||||
"-DPORT=JSCOnly",
|
||||
"-DENABLE_STATIC_JSC=ON",
|
||||
"-DALLOW_LINE_AND_COLUMN_NUMBER_IN_BUILTINS=ON",
|
||||
"-DUSE_THIN_ARCHIVES=OFF",
|
||||
"-DUSE_BUN_JSC_ADDITIONS=ON",
|
||||
"-DUSE_BUN_EVENT_LOOP=ON",
|
||||
"-DENABLE_FTL_JIT=ON",
|
||||
"-G",
|
||||
"Ninja",
|
||||
`-DCMAKE_C_COMPILER=${CC}`,
|
||||
`-DCMAKE_CXX_COMPILER=${CXX}`,
|
||||
];
|
||||
|
||||
if (IS_MAC) {
|
||||
flags.push(
|
||||
"-DENABLE_SINGLE_THREADED_VM_ENTRY_SCOPE=ON",
|
||||
"-DBUN_FAST_TLS=ON",
|
||||
"-DPTHREAD_JIT_PERMISSIONS_API=1",
|
||||
"-DUSE_PTHREAD_JIT_PERMISSIONS_API=ON",
|
||||
);
|
||||
} else if (IS_LINUX) {
|
||||
flags.push(
|
||||
"-DJSEXPORT_PRIVATE=WTF_EXPORT_DECLARATION",
|
||||
"-DUSE_VISIBILITY_ATTRIBUTE=1",
|
||||
"-DENABLE_REMOTE_INSPECTOR=ON",
|
||||
);
|
||||
}
|
||||
|
||||
return flags;
|
||||
};
|
||||
|
||||
// Build-specific CMake flags
|
||||
const getBuildFlags = (config: BuildConfig) => {
|
||||
const flags = [...getCommonFlags()];
|
||||
|
||||
switch (config) {
|
||||
case "debug":
|
||||
flags.push(
|
||||
"-DCMAKE_BUILD_TYPE=Debug",
|
||||
"-DENABLE_BUN_SKIP_FAILING_ASSERTIONS=ON",
|
||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
||||
"-DENABLE_REMOTE_INSPECTOR=ON",
|
||||
"-DUSE_VISIBILITY_ATTRIBUTE=1",
|
||||
);
|
||||
|
||||
if (IS_MAC) {
|
||||
// Enable address sanitizer by default on Mac debug builds
|
||||
flags.push("-DENABLE_SANITIZERS=address");
|
||||
// To disable asan, comment the line above and uncomment:
|
||||
// flags.push("-DENABLE_MALLOC_HEAP_BREAKDOWN=ON");
|
||||
}
|
||||
break;
|
||||
|
||||
case "lto":
|
||||
flags.push("-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_FLAGS=-flto=full", "-DCMAKE_CXX_FLAGS=-flto=full");
|
||||
break;
|
||||
|
||||
default: // release
|
||||
flags.push("-DCMAKE_BUILD_TYPE=RelWithDebInfo");
|
||||
break;
|
||||
}
|
||||
|
||||
return flags;
|
||||
};
|
||||
|
||||
// Environment variables for the build
|
||||
const getBuildEnv = () => {
|
||||
const env = { ...process.env };
|
||||
|
||||
const cflags = ["-ffat-lto-objects"];
|
||||
const cxxflags = ["-ffat-lto-objects"];
|
||||
|
||||
if (IS_LINUX && buildConfig !== "lto") {
|
||||
cflags.push("-Wl,--whole-archive");
|
||||
cxxflags.push("-Wl,--whole-archive", "-DUSE_BUN_JSC_ADDITIONS=ON", "-DUSE_BUN_EVENT_LOOP=ON");
|
||||
}
|
||||
|
||||
env.CFLAGS = (env.CFLAGS || "") + " " + cflags.join(" ");
|
||||
env.CXXFLAGS = (env.CXXFLAGS || "") + " " + cxxflags.join(" ");
|
||||
|
||||
if (IS_MAC) {
|
||||
env.ICU_INCLUDE_DIRS = `${HOMEBREW_PREFIX}opt/icu4c/include`;
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
// Run a command with proper error handling
|
||||
function runCommand(command: string, args: string[], options: any = {}) {
|
||||
console.log(`Running: ${command} ${args.join(" ")}`);
|
||||
const result = spawnSync(command, args, {
|
||||
stdio: "inherit",
|
||||
...options,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(`Failed to execute command: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
console.error(`Command failed with exit code ${result.status}`);
|
||||
process.exit(result.status || 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Main build function
|
||||
function buildJSC() {
|
||||
const buildDir = getBuildDir(buildConfig);
|
||||
const cmakeFlags = getBuildFlags(buildConfig);
|
||||
const env = getBuildEnv();
|
||||
|
||||
console.log(`Building JSC with configuration: ${buildConfig}`);
|
||||
console.log(`Build directory: ${buildDir}`);
|
||||
|
||||
// Create build directories
|
||||
if (!existsSync(buildDir)) {
|
||||
mkdirSync(buildDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (!existsSync(WEBKIT_DIR)) {
|
||||
mkdirSync(WEBKIT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Configure with CMake
|
||||
console.log("\n📦 Configuring with CMake...");
|
||||
runCommand("cmake", [...cmakeFlags, WEBKIT_DIR, buildDir], {
|
||||
cwd: buildDir,
|
||||
env,
|
||||
});
|
||||
|
||||
// Build with CMake
|
||||
console.log("\n🔨 Building JSC...");
|
||||
const buildType = buildConfig === "debug" ? "Debug" : buildConfig === "lto" ? "Release" : "RelWithDebInfo";
|
||||
|
||||
runCommand("cmake", ["--build", buildDir, "--config", buildType, "--target", "jsc"], {
|
||||
cwd: buildDir,
|
||||
env,
|
||||
});
|
||||
|
||||
console.log(`\n✅ JSC build completed successfully!`);
|
||||
console.log(`Build output: ${buildDir}`);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
if (import.meta.main) {
|
||||
buildJSC();
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun";
|
||||
|
||||
interface ReleaseInfo {
|
||||
publishedAt: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
interface Issue {
|
||||
number: number;
|
||||
closedAt: string;
|
||||
stateReason: string;
|
||||
}
|
||||
|
||||
interface Reaction {
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface Comment {
|
||||
id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get release information for a given tag
|
||||
*/
|
||||
async function getReleaseInfo(tag: string): Promise<ReleaseInfo> {
|
||||
try {
|
||||
const result = await $`gh release view ${tag} --json publishedAt,tagName`.json();
|
||||
return {
|
||||
publishedAt: result.publishedAt,
|
||||
tag: result.tagName,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get release info for ${tag}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count issues closed as completed since a given date
|
||||
*/
|
||||
async function countCompletedIssues(sinceDate: string): Promise<{ count: number; issues: number[] }> {
|
||||
try {
|
||||
const result =
|
||||
(await $`gh issue list --state closed --search "closed:>=${sinceDate} reason:completed" --limit 1000 --json number,closedAt,stateReason`.json()) as Issue[];
|
||||
|
||||
const completedIssues = result.filter(issue => issue.stateReason === "COMPLETED");
|
||||
|
||||
return {
|
||||
count: completedIssues.length,
|
||||
issues: completedIssues.map(issue => issue.number),
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to count completed issues: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get positive reactions for an issue (👍, ❤️, 🎉, 🚀)
|
||||
*/
|
||||
async function getIssueReactions(issueNumber: number): Promise<number> {
|
||||
try {
|
||||
const reactions = (await $`gh api "repos/oven-sh/bun/issues/${issueNumber}/reactions"`.json()) as Reaction[];
|
||||
return reactions.filter(r => ["+1", "heart", "hooray", "rocket"].includes(r.content)).length;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get positive reactions for all comments on an issue
|
||||
*/
|
||||
async function getCommentReactions(issueNumber: number): Promise<number> {
|
||||
try {
|
||||
const comments = (await $`gh api "repos/oven-sh/bun/issues/${issueNumber}/comments"`.json()) as Comment[];
|
||||
|
||||
let totalReactions = 0;
|
||||
for (const comment of comments) {
|
||||
try {
|
||||
const reactions =
|
||||
(await $`gh api "repos/oven-sh/bun/issues/comments/${comment.id}/reactions"`.json()) as Reaction[];
|
||||
totalReactions += reactions.filter(r => ["+1", "heart", "hooray", "rocket"].includes(r.content)).length;
|
||||
} catch {
|
||||
// Skip if we can't get reactions for this comment
|
||||
}
|
||||
}
|
||||
|
||||
return totalReactions;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count total positive reactions for issues and their comments
|
||||
*/
|
||||
async function countReactions(issueNumbers: number[], verbose = false): Promise<number> {
|
||||
let totalReactions = 0;
|
||||
|
||||
for (const issueNumber of issueNumbers) {
|
||||
if (verbose) {
|
||||
console.log(`Processing issue #${issueNumber}...`);
|
||||
}
|
||||
|
||||
const [issueReactions, commentReactions] = await Promise.all([
|
||||
getIssueReactions(issueNumber),
|
||||
getCommentReactions(issueNumber),
|
||||
]);
|
||||
|
||||
const issueTotal = issueReactions + commentReactions;
|
||||
totalReactions += issueTotal;
|
||||
|
||||
if (verbose && issueTotal > 0) {
|
||||
console.log(
|
||||
` Issue #${issueNumber}: ${issueReactions} issue + ${commentReactions} comment = ${issueTotal} total`,
|
||||
);
|
||||
}
|
||||
|
||||
// Small delay to avoid rate limiting
|
||||
await Bun.sleep(50);
|
||||
}
|
||||
|
||||
return totalReactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to collect GitHub metrics
|
||||
*/
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const releaseTag = args[0];
|
||||
const verbose = args.includes("--verbose") || args.includes("-v");
|
||||
|
||||
if (!releaseTag) {
|
||||
console.error("Usage: bun run scripts/github-metrics.ts <release-tag> [--verbose]");
|
||||
console.error("Example: bun run scripts/github-metrics.ts bun-v1.2.19");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`📊 Collecting GitHub metrics since ${releaseTag}...`);
|
||||
|
||||
// Get release date
|
||||
const releaseInfo = await getReleaseInfo(releaseTag);
|
||||
const releaseDate = releaseInfo.publishedAt.split("T")[0]; // Extract date part
|
||||
|
||||
if (verbose) {
|
||||
console.log(`📅 Release date: ${releaseDate}`);
|
||||
}
|
||||
|
||||
// Count completed issues
|
||||
console.log("🔍 Counting completed issues...");
|
||||
const { count: issueCount, issues: issueNumbers } = await countCompletedIssues(releaseDate);
|
||||
|
||||
// Count reactions
|
||||
console.log("👍 Counting positive reactions...");
|
||||
const reactionCount = await countReactions(issueNumbers, verbose);
|
||||
|
||||
// Display results
|
||||
console.log("\n📈 Results:");
|
||||
console.log(`Issues closed as completed since ${releaseTag}: ${issueCount}`);
|
||||
console.log(`Total positive reactions (👍❤️🎉🚀): ${reactionCount}`);
|
||||
|
||||
if (issueCount > 0) {
|
||||
console.log(`Average reactions per completed issue: ${(reactionCount / issueCount).toFixed(1)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if this script is executed directly
|
||||
if (import.meta.main) {
|
||||
main();
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run clang-format on all C++ source and header files in the Bun project
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Get the project root directory (parent of scripts/)
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Default to format mode (modify files)
|
||||
MODE="${1:-format}"
|
||||
|
||||
# Use LLVM_VERSION_MAJOR from environment or default to 19
|
||||
LLVM_VERSION="${LLVM_VERSION_MAJOR:-19}"
|
||||
|
||||
# Ensure we have the specific clang-format version
|
||||
CLANG_FORMAT="clang-format-${LLVM_VERSION}"
|
||||
if ! command -v "$CLANG_FORMAT" &> /dev/null; then
|
||||
echo "Error: $CLANG_FORMAT not found" >&2
|
||||
echo "Please install clang-format version $LLVM_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Array to hold all files to format
|
||||
declare -a FILES_TO_FORMAT
|
||||
|
||||
# Find all header files in src/ and packages/, excluding third-party and generated code
|
||||
echo "Finding header files..."
|
||||
while IFS= read -r -d '' file; do
|
||||
# Additional filtering for specific files and patterns
|
||||
if [[ "$file" =~ src/bun\.js/api/ffi- ]] || \
|
||||
[[ "$file" =~ src/napi/ ]] || \
|
||||
[[ "$file" =~ src/bun\.js/bindings/libuv/ ]] || \
|
||||
[[ "$file" =~ src/bun\.js/bindings/sqlite/ ]] || \
|
||||
[[ "$file" =~ packages/bun-usockets/.*libuv ]] || \
|
||||
[[ "$file" =~ src/deps/ ]]; then
|
||||
continue
|
||||
fi
|
||||
FILES_TO_FORMAT+=("$file")
|
||||
done < <(find src packages -type f \( -name "*.h" -o -name "*.hpp" \) \
|
||||
-not -path "*/vendor/*" \
|
||||
-not -path "*/third_party/*" \
|
||||
-not -path "*/thirdparty/*" \
|
||||
-not -path "*/generated/*" \
|
||||
-print0 2>/dev/null || true)
|
||||
|
||||
# Read C++ source files from CxxSources.txt
|
||||
echo "Reading C++ source files from CxxSources.txt..."
|
||||
if [ -f "cmake/sources/CxxSources.txt" ]; then
|
||||
while IFS= read -r file; do
|
||||
# Skip empty lines and comments
|
||||
if [[ -n "$file" && ! "$file" =~ ^[[:space:]]*# ]]; then
|
||||
# Check if file exists
|
||||
if [ -f "$file" ]; then
|
||||
FILES_TO_FORMAT+=("$file")
|
||||
fi
|
||||
fi
|
||||
done < "cmake/sources/CxxSources.txt"
|
||||
else
|
||||
echo "Warning: cmake/sources/CxxSources.txt not found" >&2
|
||||
fi
|
||||
|
||||
# Remove duplicates while preserving order
|
||||
declare -a UNIQUE_FILES
|
||||
declare -A seen
|
||||
for file in "${FILES_TO_FORMAT[@]}"; do
|
||||
if [[ ! -v "seen[$file]" ]]; then
|
||||
seen["$file"]=1
|
||||
UNIQUE_FILES+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Processing ${#UNIQUE_FILES[@]} files..."
|
||||
|
||||
# Run clang-format based on mode
|
||||
if [ "$MODE" = "check" ]; then
|
||||
# Check mode - verify formatting without modifying files
|
||||
FAILED=0
|
||||
for file in "${UNIQUE_FILES[@]}"; do
|
||||
# Find the nearest .clang-format file for this source file
|
||||
dir=$(dirname "$file")
|
||||
while [ "$dir" != "." ] && [ "$dir" != "/" ]; do
|
||||
if [ -f "$dir/.clang-format" ]; then
|
||||
break
|
||||
fi
|
||||
dir=$(dirname "$dir")
|
||||
done
|
||||
|
||||
if ! $CLANG_FORMAT --dry-run --Werror "$file" 2>/dev/null; then
|
||||
echo "Format check failed: $file"
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "Some files need formatting. Run 'bun run clang-format' to fix."
|
||||
exit 1
|
||||
else
|
||||
echo "All files are properly formatted."
|
||||
fi
|
||||
elif [ "$MODE" = "format" ] || [ "$MODE" = "fix" ]; then
|
||||
# Format mode - modify files in place
|
||||
for file in "${UNIQUE_FILES[@]}"; do
|
||||
echo "Formatting: $file"
|
||||
$CLANG_FORMAT -i "$file"
|
||||
done
|
||||
echo "Formatting complete."
|
||||
elif [ "$MODE" = "diff" ]; then
|
||||
# Diff mode - show what would change
|
||||
for file in "${UNIQUE_FILES[@]}"; do
|
||||
if ! $CLANG_FORMAT --dry-run --Werror "$file" 2>/dev/null; then
|
||||
echo "=== $file ==="
|
||||
diff -u "$file" <($CLANG_FORMAT "$file") || true
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "Usage: $0 [check|format|fix|diff]" >&2
|
||||
echo " check - Check if files are formatted (default)" >&2
|
||||
echo " format - Format files in place" >&2
|
||||
echo " fix - Same as format" >&2
|
||||
echo " diff - Show formatting differences" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -82,10 +82,6 @@ function getNodeParallelTestTimeout(testPath) {
|
||||
return 10_000;
|
||||
}
|
||||
|
||||
process.on("SIGTRAP", () => {
|
||||
console.warn("Test runner received SIGTRAP. Doing nothing.");
|
||||
});
|
||||
|
||||
const { values: options, positionals: filters } = parseArgs({
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
@@ -182,37 +178,6 @@ if (options["quiet"]) {
|
||||
isQuiet = true;
|
||||
}
|
||||
|
||||
let newFiles = [];
|
||||
let prFileCount = 0;
|
||||
if (isBuildkite) {
|
||||
try {
|
||||
console.log("on buildkite: collecting new files from PR");
|
||||
const per_page = 50;
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/oven-sh/bun/pulls/${process.env.BUILDKITE_PULL_REQUEST}/files?per_page=${per_page}&page=${i}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${getSecret("GITHUB_TOKEN")}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const doc = await res.json();
|
||||
console.log(`-> page ${i}, found ${doc.length} items`);
|
||||
if (doc.length === 0) break;
|
||||
if (doc.length < per_page) break;
|
||||
for (const { filename, status } of doc) {
|
||||
prFileCount += 1;
|
||||
if (status !== "added") continue;
|
||||
newFiles.push(filename);
|
||||
}
|
||||
}
|
||||
console.log(`- PR ${process.env.BUILDKITE_PULL_REQUEST}, ${prFileCount} files, ${newFiles.length} new files`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
let coresDir;
|
||||
|
||||
if (options["coredump-upload"]) {
|
||||
@@ -455,7 +420,6 @@ async function runTests() {
|
||||
if (attempt >= maxAttempts || isAlwaysFailure(error)) {
|
||||
flaky = false;
|
||||
failedResults.push(failure);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2017,9 +1981,6 @@ function formatTestToMarkdown(result, concise, retries) {
|
||||
if (retries > 0) {
|
||||
markdown += ` (${retries} ${retries === 1 ? "retry" : "retries"})`;
|
||||
}
|
||||
if (newFiles.includes(testTitle)) {
|
||||
markdown += ` (new)`;
|
||||
}
|
||||
|
||||
if (concise) {
|
||||
markdown += "</li>\n";
|
||||
@@ -2225,7 +2186,6 @@ function isAlwaysFailure(error) {
|
||||
error.includes("illegal instruction") ||
|
||||
error.includes("sigtrap") ||
|
||||
error.includes("error: addresssanitizer") ||
|
||||
error.includes("internal assertion failure") ||
|
||||
error.includes("core dumped") ||
|
||||
error.includes("crash reported")
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ const usage = String.raw`
|
||||
/_____ \____/|__| |__| /_____ \__|__|_| / __/ \____/|__| |__| /____ >
|
||||
\/ \/ \/|__| \/
|
||||
|
||||
Usage: bun scripts/sort-imports [options] <files...>
|
||||
Usage: bun scripts/sortImports [options] <files...>
|
||||
|
||||
Options:
|
||||
--help Show this help message
|
||||
@@ -22,7 +22,7 @@ Options:
|
||||
--keep-unused Don't remove unused imports
|
||||
|
||||
Examples:
|
||||
bun scripts/sort-imports src
|
||||
bun scripts/sortImports src
|
||||
`.slice(1);
|
||||
if (args.includes("--help")) {
|
||||
console.log(usage);
|
||||
|
||||
@@ -47,7 +47,7 @@ fn createImportRecord(this: *HTMLScanner, input_path: []const u8, kind: ImportKi
|
||||
try this.import_records.push(this.allocator, record);
|
||||
}
|
||||
|
||||
const debug = bun.Output.scoped(.HTMLScanner, .hidden);
|
||||
const debug = bun.Output.scoped(.HTMLScanner, true);
|
||||
|
||||
pub fn onWriteHTML(_: *HTMLScanner, bytes: []const u8) void {
|
||||
_ = bytes; // bytes are not written in scan phase
|
||||
|
||||
@@ -6,7 +6,6 @@ pub const StandaloneModuleGraph = struct {
|
||||
bytes: []const u8 = "",
|
||||
files: bun.StringArrayHashMap(File),
|
||||
entry_point_id: u32 = 0,
|
||||
compile_exec_argv: []const u8 = "",
|
||||
|
||||
// We never want to hit the filesystem for these files
|
||||
// We use the `/$bunfs/` prefix to indicate that it's a virtual path
|
||||
@@ -280,7 +279,6 @@ pub const StandaloneModuleGraph = struct {
|
||||
byte_count: usize = 0,
|
||||
modules_ptr: bun.StringPointer = .{},
|
||||
entry_point_id: u32 = 0,
|
||||
compile_exec_argv_ptr: bun.StringPointer = .{},
|
||||
};
|
||||
|
||||
const trailer = "\n---- Bun! ----\n";
|
||||
@@ -325,7 +323,6 @@ pub const StandaloneModuleGraph = struct {
|
||||
.bytes = raw_bytes[0..offsets.byte_count],
|
||||
.files = modules,
|
||||
.entry_point_id = offsets.entry_point_id,
|
||||
.compile_exec_argv = sliceToZ(raw_bytes, offsets.compile_exec_argv_ptr),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -341,7 +338,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
return bytes[ptr.offset..][0..ptr.length :0];
|
||||
}
|
||||
|
||||
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format, compile_exec_argv: []const u8) ![]u8 {
|
||||
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format) ![]u8 {
|
||||
var serialize_trace = bun.perf.trace("StandaloneModuleGraph.serialize");
|
||||
defer serialize_trace.end();
|
||||
|
||||
@@ -382,7 +379,6 @@ pub const StandaloneModuleGraph = struct {
|
||||
string_builder.cap += trailer.len;
|
||||
string_builder.cap += 16;
|
||||
string_builder.cap += @sizeOf(Offsets);
|
||||
string_builder.countZ(compile_exec_argv);
|
||||
|
||||
try string_builder.allocate(allocator);
|
||||
|
||||
@@ -467,7 +463,6 @@ pub const StandaloneModuleGraph = struct {
|
||||
const offsets = Offsets{
|
||||
.entry_point_id = @as(u32, @truncate(entry_point_id.?)),
|
||||
.modules_ptr = string_builder.appendCount(std.mem.sliceAsBytes(modules.items)),
|
||||
.compile_exec_argv_ptr = string_builder.appendCountZ(compile_exec_argv),
|
||||
.byte_count = string_builder.len,
|
||||
};
|
||||
|
||||
@@ -838,9 +833,8 @@ pub const StandaloneModuleGraph = struct {
|
||||
output_format: bun.options.Format,
|
||||
windows_hide_console: bool,
|
||||
windows_icon: ?[]const u8,
|
||||
compile_exec_argv: []const u8,
|
||||
) !void {
|
||||
const bytes = try toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv);
|
||||
const bytes = try toBytes(allocator, module_prefix, output_files, output_format);
|
||||
if (bytes.len == 0) return;
|
||||
|
||||
const fd = inject(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const Watcher = @This();
|
||||
|
||||
const DebugLogScope = bun.Output.Scoped(.watcher, .visible);
|
||||
const DebugLogScope = bun.Output.Scoped(.watcher, false);
|
||||
const log = DebugLogScope.log;
|
||||
|
||||
// This will always be [max_count]WatchEvent,
|
||||
@@ -32,7 +32,7 @@ ctx: *anyopaque,
|
||||
onFileUpdate: *const fn (this: *anyopaque, events: []WatchEvent, changed_files: []?[:0]u8, watchlist: WatchList) void,
|
||||
onError: *const fn (this: *anyopaque, err: bun.sys.Error) void,
|
||||
|
||||
thread_lock: bun.safety.ThreadLock = .initUnlocked(),
|
||||
thread_lock: bun.DebugThreadLock = bun.DebugThreadLock.unlocked,
|
||||
|
||||
pub const max_count = 128;
|
||||
pub const requires_file_descriptors = switch (Environment.os) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
pub const c_allocator = basic.c_allocator;
|
||||
pub const z_allocator = basic.z_allocator;
|
||||
pub const freeWithoutSize = basic.freeWithoutSize;
|
||||
pub const c_allocator = @import("./allocators/basic.zig").c_allocator;
|
||||
pub const z_allocator = @import("./allocators/basic.zig").z_allocator;
|
||||
pub const mimalloc = @import("./allocators/mimalloc.zig");
|
||||
pub const MimallocArena = @import("./allocators/MimallocArena.zig");
|
||||
pub const AllocationScope = @import("./allocators/AllocationScope.zig");
|
||||
@@ -226,6 +225,7 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
|
||||
}
|
||||
};
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Self = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
@@ -311,6 +311,7 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
|
||||
|
||||
return struct {
|
||||
pub const Overflow = OverflowList([]const u8, count / 4);
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Self = @This();
|
||||
|
||||
backing_buf: [count * item_length]u8,
|
||||
@@ -494,6 +495,7 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
|
||||
pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_keys: bool, comptime estimated_key_length: usize, comptime remove_trailing_slashes: bool) type {
|
||||
const max_index = count - 1;
|
||||
const BSSMapType = struct {
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Self = @This();
|
||||
const Overflow = OverflowList(ValueType, count / 4);
|
||||
|
||||
@@ -770,44 +772,8 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isDefault(allocator: Allocator) bool {
|
||||
return allocator.vtable == c_allocator.vtable;
|
||||
}
|
||||
|
||||
/// Allocate memory for a value of type `T` using the provided allocator, and initialize the memory
|
||||
/// with `value`.
|
||||
///
|
||||
/// If `allocator` is `bun.default_allocator`, this will internally use `bun.tryNew` to benefit from
|
||||
/// the added assertions.
|
||||
pub fn create(comptime T: type, allocator: Allocator, value: T) OOM!*T {
|
||||
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
|
||||
return bun.tryNew(T, value);
|
||||
}
|
||||
const ptr = try allocator.create(T);
|
||||
ptr.* = value;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// Free memory previously allocated by `create`.
|
||||
///
|
||||
/// The memory must have been allocated by the `create` function in this namespace, not
|
||||
/// directly by `allocator.create`.
|
||||
pub fn destroy(allocator: Allocator, ptr: anytype) void {
|
||||
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
|
||||
bun.destroy(ptr);
|
||||
} else {
|
||||
allocator.destroy(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
const basic = if (bun.use_mimalloc)
|
||||
@import("./allocators/basic.zig")
|
||||
else
|
||||
@import("./allocators/fallback.zig");
|
||||
|
||||
const Environment = @import("./env.zig");
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const bun = @import("bun");
|
||||
const OOM = bun.OOM;
|
||||
|
||||
@@ -252,12 +252,6 @@ pub inline fn downcast(a: Allocator) ?*AllocationScope {
|
||||
null;
|
||||
}
|
||||
|
||||
pub fn leakSlice(scope: *AllocationScope, memory: anytype) void {
|
||||
if (comptime !enabled) return;
|
||||
_ = @typeInfo(@TypeOf(memory)).pointer;
|
||||
bun.assert(!scope.trackExternalFree(memory, null));
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const MemoryReportingAllocator = @This();
|
||||
|
||||
const log = bun.Output.scoped(.MEM, .visible);
|
||||
const log = bun.Output.scoped(.MEM, false);
|
||||
|
||||
child_allocator: std.mem.Allocator,
|
||||
memory_cost: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
|
||||
|
||||
@@ -1,58 +1,29 @@
|
||||
const Self = @This();
|
||||
|
||||
heap: HeapPtr,
|
||||
heap: ?*mimalloc.Heap = null,
|
||||
|
||||
const HeapPtr = if (safety_checks) *DebugHeap else *mimalloc.Heap;
|
||||
|
||||
const DebugHeap = struct {
|
||||
inner: *mimalloc.Heap,
|
||||
thread_lock: bun.safety.ThreadLock,
|
||||
};
|
||||
|
||||
fn getMimallocHeap(self: Self) *mimalloc.Heap {
|
||||
return if (comptime safety_checks) self.heap.inner else self.heap;
|
||||
}
|
||||
|
||||
fn fromOpaque(ptr: *anyopaque) Self {
|
||||
return .{ .heap = bun.cast(HeapPtr, ptr) };
|
||||
}
|
||||
|
||||
fn assertThreadLock(self: Self) void {
|
||||
if (comptime safety_checks) self.heap.thread_lock.assertLocked();
|
||||
}
|
||||
|
||||
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
|
||||
|
||||
fn getThreadHeap() HeapPtr {
|
||||
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
|
||||
if (thread_heap == null) {
|
||||
thread_heap = .{
|
||||
.inner = mimalloc.mi_heap_get_default(),
|
||||
.thread_lock = .initLocked(),
|
||||
};
|
||||
}
|
||||
return &thread_heap.?;
|
||||
}
|
||||
|
||||
const log = bun.Output.scoped(.mimalloc, .hidden);
|
||||
const log = bun.Output.scoped(.mimalloc, true);
|
||||
|
||||
/// Internally, mimalloc calls mi_heap_get_default()
|
||||
/// to get the default heap.
|
||||
/// It uses pthread_getspecific to do that.
|
||||
/// We can save those extra calls if we just do it once in here
|
||||
pub fn getThreadLocalDefault() Allocator {
|
||||
return Allocator{ .ptr = getThreadHeap(), .vtable = &c_allocator_vtable };
|
||||
pub fn getThreadlocalDefault() Allocator {
|
||||
return Allocator{ .ptr = mimalloc.mi_heap_get_default(), .vtable = &c_allocator_vtable };
|
||||
}
|
||||
|
||||
pub fn backingAllocator(_: Self) Allocator {
|
||||
return getThreadLocalDefault();
|
||||
pub fn backingAllocator(self: Self) Allocator {
|
||||
var arena = Self{ .heap = self.heap.?.backing() };
|
||||
return arena.allocator();
|
||||
}
|
||||
|
||||
pub fn allocator(self: Self) Allocator {
|
||||
return Allocator{ .ptr = self.heap, .vtable = &c_allocator_vtable };
|
||||
@setRuntimeSafety(false);
|
||||
return Allocator{ .ptr = self.heap.?, .vtable = &c_allocator_vtable };
|
||||
}
|
||||
|
||||
pub fn dumpThreadStats(_: *Self) void {
|
||||
pub fn dumpThreadStats(self: *Self) void {
|
||||
_ = self;
|
||||
const dump_fn = struct {
|
||||
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
|
||||
const text = bun.span(textZ);
|
||||
@@ -63,7 +34,8 @@ pub fn dumpThreadStats(_: *Self) void {
|
||||
bun.Output.flush();
|
||||
}
|
||||
|
||||
pub fn dumpStats(_: *Self) void {
|
||||
pub fn dumpStats(self: *Self) void {
|
||||
_ = self;
|
||||
const dump_fn = struct {
|
||||
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
|
||||
const text = bun.span(textZ);
|
||||
@@ -75,51 +47,37 @@ pub fn dumpStats(_: *Self) void {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
const mimalloc_heap = self.getMimallocHeap();
|
||||
if (comptime safety_checks) {
|
||||
bun.destroy(self.heap);
|
||||
}
|
||||
mimalloc.mi_heap_destroy(mimalloc_heap);
|
||||
self.* = undefined;
|
||||
mimalloc.mi_heap_destroy(bun.take(&self.heap).?);
|
||||
}
|
||||
|
||||
pub fn init() Self {
|
||||
const mimalloc_heap = mimalloc.mi_heap_new() orelse bun.outOfMemory();
|
||||
const heap = if (comptime safety_checks)
|
||||
bun.new(DebugHeap, .{
|
||||
.inner = mimalloc_heap,
|
||||
.thread_lock = .initLocked(),
|
||||
})
|
||||
else
|
||||
mimalloc_heap;
|
||||
return .{ .heap = heap };
|
||||
pub fn init() !Self {
|
||||
return .{ .heap = mimalloc.mi_heap_new() orelse return error.OutOfMemory };
|
||||
}
|
||||
|
||||
pub fn gc(self: Self) void {
|
||||
mimalloc.mi_heap_collect(self.getMimallocHeap(), false);
|
||||
mimalloc.mi_heap_collect(self.heap orelse return, false);
|
||||
}
|
||||
|
||||
pub inline fn helpCatchMemoryIssues(self: Self) void {
|
||||
if (comptime bun.FeatureFlags.help_catch_memory_issues) {
|
||||
if (comptime FeatureFlags.help_catch_memory_issues) {
|
||||
self.gc();
|
||||
bun.mimalloc.mi_collect(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ownsPtr(self: Self, ptr: *const anyopaque) bool {
|
||||
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
|
||||
return mimalloc.mi_heap_check_owned(self.heap.?, ptr);
|
||||
}
|
||||
pub const supports_posix_memalign = true;
|
||||
|
||||
fn alignedAlloc(self: Self, len: usize, alignment: Alignment) ?[*]u8 {
|
||||
fn alignedAlloc(heap: *mimalloc.Heap, len: usize, alignment: mem.Alignment) ?[*]u8 {
|
||||
log("Malloc: {d}\n", .{len});
|
||||
|
||||
const heap = self.getMimallocHeap();
|
||||
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
|
||||
else
|
||||
mimalloc.mi_heap_malloc(heap, len);
|
||||
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
if (comptime Environment.isDebug) {
|
||||
const usable = mimalloc.mi_malloc_usable_size(ptr);
|
||||
if (usable < len) {
|
||||
std.debug.panic("mimalloc: allocated size is too small: {d} < {d}", .{ usable, len });
|
||||
@@ -136,28 +94,30 @@ fn alignedAllocSize(ptr: [*]u8) usize {
|
||||
return mimalloc.mi_malloc_usable_size(ptr);
|
||||
}
|
||||
|
||||
fn alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
const self = fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
return alignedAlloc(self, len, alignment);
|
||||
fn alloc(arena: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
|
||||
const self = bun.cast(*mimalloc.Heap, arena);
|
||||
|
||||
return alignedAlloc(
|
||||
self,
|
||||
len,
|
||||
alignment,
|
||||
);
|
||||
}
|
||||
|
||||
fn resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
const self = fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
fn resize(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
|
||||
return mimalloc.mi_expand(buf.ptr, new_len) != null;
|
||||
}
|
||||
|
||||
fn free(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
alignment: Alignment,
|
||||
alignment: mem.Alignment,
|
||||
_: usize,
|
||||
) void {
|
||||
// mi_free_size internally just asserts the size
|
||||
// so it's faster if we don't pass that value through
|
||||
// but its good to have that assertion
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
if (comptime Environment.isDebug) {
|
||||
assert(mimalloc.mi_is_in_heap_region(buf.ptr));
|
||||
if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
mimalloc.mi_free_size_aligned(buf.ptr, buf.len, alignment.toByteUnits())
|
||||
@@ -187,12 +147,9 @@ fn free(
|
||||
/// `ret_addr` is optionally provided as the first return address of the
|
||||
/// allocation call stack. If the value is `0` it means no return address
|
||||
/// has been provided.
|
||||
fn remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
const self = fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
const heap = self.getMimallocHeap();
|
||||
fn remap(self: *anyopaque, buf: []u8, alignment: mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
const aligned_size = alignment.toByteUnits();
|
||||
const value = mimalloc.mi_heap_realloc_aligned(heap, buf.ptr, new_len, aligned_size);
|
||||
const value = mimalloc.mi_heap_realloc_aligned(@ptrCast(self), buf.ptr, new_len, aligned_size);
|
||||
return @ptrCast(value);
|
||||
}
|
||||
|
||||
@@ -207,12 +164,13 @@ const c_allocator_vtable = Allocator.VTable{
|
||||
.free = &Self.free,
|
||||
};
|
||||
|
||||
const Environment = @import("../env.zig");
|
||||
const FeatureFlags = @import("../feature_flags.zig");
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const assert = bun.assert;
|
||||
const mimalloc = bun.mimalloc;
|
||||
const safety_checks = bun.Environment.ci_assert;
|
||||
|
||||
const Alignment = std.mem.Alignment;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const log = bun.Output.scoped(.mimalloc, .hidden);
|
||||
const log = bun.Output.scoped(.mimalloc, true);
|
||||
|
||||
fn mimalloc_free(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
alignment: Alignment,
|
||||
alignment: mem.Alignment,
|
||||
_: usize,
|
||||
) void {
|
||||
if (comptime Environment.enable_logs)
|
||||
@@ -23,7 +23,8 @@ fn mimalloc_free(
|
||||
}
|
||||
|
||||
const MimallocAllocator = struct {
|
||||
fn alignedAlloc(len: usize, alignment: Alignment) ?[*]u8 {
|
||||
pub const supports_posix_memalign = true;
|
||||
fn alignedAlloc(len: usize, alignment: mem.Alignment) ?[*]u8 {
|
||||
if (comptime Environment.enable_logs)
|
||||
log("mi_alloc({d}, {d})", .{ len, alignment.toByteUnits() });
|
||||
|
||||
@@ -48,15 +49,15 @@ const MimallocAllocator = struct {
|
||||
return mimalloc.mi_malloc_size(ptr);
|
||||
}
|
||||
|
||||
fn alloc_with_default_allocator(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
fn alloc_with_default_allocator(_: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
|
||||
return alignedAlloc(len, alignment);
|
||||
}
|
||||
|
||||
fn resize_with_default_allocator(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
fn resize_with_default_allocator(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
|
||||
return mimalloc.mi_expand(buf.ptr, new_len) != null;
|
||||
}
|
||||
|
||||
fn remap_with_default_allocator(_: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
fn remap_with_default_allocator(_: *anyopaque, buf: []u8, alignment: mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
return @ptrCast(mimalloc.mi_realloc_aligned(buf.ptr, new_len, alignment.toByteUnits()));
|
||||
}
|
||||
|
||||
@@ -76,7 +77,9 @@ const c_allocator_vtable = &Allocator.VTable{
|
||||
};
|
||||
|
||||
const ZAllocator = struct {
|
||||
fn alignedAlloc(len: usize, alignment: Alignment) ?[*]u8 {
|
||||
pub const supports_posix_memalign = true;
|
||||
|
||||
fn alignedAlloc(len: usize, alignment: mem.Alignment) ?[*]u8 {
|
||||
log("ZAllocator.alignedAlloc: {d}\n", .{len});
|
||||
|
||||
const ptr = if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
@@ -100,11 +103,11 @@ const ZAllocator = struct {
|
||||
return mimalloc.mi_malloc_size(ptr);
|
||||
}
|
||||
|
||||
fn alloc_with_z_allocator(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
fn alloc_with_z_allocator(_: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
|
||||
return alignedAlloc(len, alignment);
|
||||
}
|
||||
|
||||
fn resize_with_z_allocator(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
fn resize_with_z_allocator(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
|
||||
if (new_len <= buf.len) {
|
||||
return true;
|
||||
}
|
||||
@@ -135,20 +138,15 @@ pub const z_allocator = Allocator{
|
||||
const z_allocator_vtable = Allocator.VTable{
|
||||
.alloc = &ZAllocator.alloc_with_z_allocator,
|
||||
.resize = &ZAllocator.resize_with_z_allocator,
|
||||
.remap = &Allocator.noRemap,
|
||||
.remap = &std.mem.Allocator.noRemap,
|
||||
.free = &ZAllocator.free_with_z_allocator,
|
||||
};
|
||||
|
||||
/// mimalloc can free allocations without being given their size.
|
||||
pub fn freeWithoutSize(ptr: ?*anyopaque) void {
|
||||
mimalloc.mi_free(ptr);
|
||||
}
|
||||
|
||||
const Environment = @import("../env.zig");
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const mimalloc = bun.mimalloc;
|
||||
|
||||
const Alignment = std.mem.Alignment;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const mem = @import("std").mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
pub const c_allocator = std.heap.c_allocator;
|
||||
pub const z_allocator = @import("./fallback/z.zig").allocator;
|
||||
|
||||
/// libc can free allocations without being given their size.
|
||||
pub fn freeWithoutSize(ptr: ?*anyopaque) void {
|
||||
std.c.free(ptr);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
@@ -1,43 +0,0 @@
|
||||
/// A fallback zero-initializing allocator.
|
||||
pub const allocator = Allocator{
|
||||
.ptr = undefined,
|
||||
.vtable = &vtable,
|
||||
};
|
||||
|
||||
const vtable = Allocator.VTable{
|
||||
.alloc = alloc,
|
||||
.resize = resize,
|
||||
.remap = Allocator.noRemap, // the mimalloc z_allocator doesn't support remap
|
||||
.free = free,
|
||||
};
|
||||
|
||||
fn alloc(_: *anyopaque, len: usize, alignment: Alignment, return_address: usize) ?[*]u8 {
|
||||
const result = c_allocator.rawAlloc(len, alignment, return_address) orelse
|
||||
return null;
|
||||
@memset(result[0..len], 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn resize(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
alignment: Alignment,
|
||||
new_len: usize,
|
||||
return_address: usize,
|
||||
) bool {
|
||||
if (!c_allocator.rawResize(buf, alignment, new_len, return_address)) {
|
||||
return false;
|
||||
}
|
||||
@memset(buf.ptr[buf.len..new_len], 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
fn free(_: *anyopaque, buf: []u8, alignment: Alignment, return_address: usize) void {
|
||||
c_allocator.rawFree(buf, alignment, return_address);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const c_allocator = std.heap.c_allocator;
|
||||
|
||||
const Alignment = std.mem.Alignment;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -52,6 +52,10 @@ pub const Heap = opaque {
|
||||
return mi_heap_malloc(self, size);
|
||||
}
|
||||
|
||||
pub fn backing(_: *Heap) *Heap {
|
||||
return mi_heap_get_default();
|
||||
}
|
||||
|
||||
pub fn calloc(self: *Heap, count: usize, size: usize) ?*anyopaque {
|
||||
return mi_heap_calloc(self, count, size);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@ pub const Features = struct {
|
||||
pub var csrf_generate: usize = 0;
|
||||
pub var unsupported_uv_function: usize = 0;
|
||||
pub var exited: usize = 0;
|
||||
pub var yarn_migration: usize = 0;
|
||||
|
||||
comptime {
|
||||
@export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" });
|
||||
|
||||
@@ -1,526 +0,0 @@
|
||||
last_part: *js_ast.Part,
|
||||
// files in node modules will not get hot updates, so the code generation
|
||||
// can be a bit more concise for re-exports
|
||||
is_in_node_modules: bool,
|
||||
imports_seen: bun.StringArrayHashMapUnmanaged(ImportRef) = .{},
|
||||
export_star_props: std.ArrayListUnmanaged(G.Property) = .{},
|
||||
export_props: std.ArrayListUnmanaged(G.Property) = .{},
|
||||
stmts: std.ArrayListUnmanaged(Stmt) = .{},
|
||||
|
||||
const ImportRef = struct {
|
||||
/// Index into ConvertESMExportsForHmr.stmts
|
||||
stmt_index: u32,
|
||||
};
|
||||
|
||||
pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void {
|
||||
const new_stmt = switch (stmt.data) {
|
||||
else => brk: {
|
||||
break :brk stmt;
|
||||
},
|
||||
.s_local => |st| stmt: {
|
||||
if (!st.is_export) {
|
||||
break :stmt stmt;
|
||||
}
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
var new_len: usize = 0;
|
||||
for (st.decls.slice()) |*decl_ptr| {
|
||||
const decl = decl_ptr.*; // explicit copy to avoid aliasinng
|
||||
const value = decl.value orelse {
|
||||
st.decls.mut(new_len).* = decl;
|
||||
new_len += 1;
|
||||
try ctx.visitBindingToExport(p, decl.binding);
|
||||
continue;
|
||||
};
|
||||
|
||||
switch (decl.binding.data) {
|
||||
.b_missing => {},
|
||||
|
||||
.b_identifier => |id| {
|
||||
const symbol = p.symbols.items[id.ref.inner_index];
|
||||
|
||||
// if the symbol is not used, we don't need to preserve
|
||||
// a binding in this scope. we can move it to the exports object.
|
||||
if (symbol.use_count_estimate == 0 and value.canBeMoved()) {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = symbol.original_name }, decl.binding.loc),
|
||||
.value = value,
|
||||
});
|
||||
} else {
|
||||
st.decls.mut(new_len).* = decl;
|
||||
new_len += 1;
|
||||
try ctx.visitBindingToExport(p, decl.binding);
|
||||
}
|
||||
},
|
||||
|
||||
else => {
|
||||
st.decls.mut(new_len).* = decl;
|
||||
new_len += 1;
|
||||
try ctx.visitBindingToExport(p, decl.binding);
|
||||
},
|
||||
}
|
||||
}
|
||||
if (new_len == 0) {
|
||||
return;
|
||||
}
|
||||
st.decls.len = @intCast(new_len);
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_default => |st| stmt: {
|
||||
// When React Fast Refresh needs to tag the default export, the statement
|
||||
// cannot be moved, since a local reference is required.
|
||||
if (p.options.features.react_fast_refresh and
|
||||
st.value == .stmt and st.value.stmt.data == .s_function)
|
||||
fast_refresh_edge_case: {
|
||||
const symbol = st.value.stmt.data.s_function.func.name orelse
|
||||
break :fast_refresh_edge_case;
|
||||
const name = p.symbols.items[symbol.ref.?.inner_index].original_name;
|
||||
if (ReactRefresh.isComponentishName(name)) {
|
||||
// Lower to a function statement, and reference the function in the export list.
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(symbol.ref.?, stmt.loc),
|
||||
});
|
||||
break :stmt st.value.stmt;
|
||||
}
|
||||
// All other functions can be properly moved.
|
||||
}
|
||||
|
||||
// Try to move the export default expression to the end.
|
||||
const can_be_moved_to_inner_scope = switch (st.value) {
|
||||
.stmt => |s| switch (s.data) {
|
||||
.s_class => |c| c.class.canBeMoved() and (if (c.class.class_name) |name|
|
||||
p.symbols.items[name.ref.?.inner_index].use_count_estimate == 0
|
||||
else
|
||||
true),
|
||||
.s_function => |f| if (f.func.name) |name|
|
||||
p.symbols.items[name.ref.?.inner_index].use_count_estimate == 0
|
||||
else
|
||||
true,
|
||||
else => unreachable,
|
||||
},
|
||||
.expr => |e| switch (e.data) {
|
||||
.e_identifier => true,
|
||||
else => e.canBeMoved(),
|
||||
},
|
||||
};
|
||||
if (can_be_moved_to_inner_scope) {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
});
|
||||
// no statement emitted
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, an identifier must be exported
|
||||
switch (st.value) {
|
||||
.expr => {
|
||||
const temp_id = p.generateTempRef("default_export");
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = temp_id, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, temp_id, .{ .count_estimate = 1 });
|
||||
try p.current_scope.generated.push(p.allocator, temp_id);
|
||||
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(temp_id, stmt.loc),
|
||||
});
|
||||
|
||||
break :stmt Stmt.alloc(S.Local, .{
|
||||
.kind = .k_const,
|
||||
.decls = try G.Decl.List.fromSlice(p.allocator, &.{
|
||||
.{
|
||||
.binding = Binding.alloc(p.allocator, B.Identifier{ .ref = temp_id }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
},
|
||||
}),
|
||||
}, stmt.loc);
|
||||
},
|
||||
.stmt => |s| {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(switch (s.data) {
|
||||
.s_class => |class| class.class.class_name.?.ref.?,
|
||||
.s_function => |func| func.func.name.?.ref.?,
|
||||
else => unreachable,
|
||||
}, stmt.loc),
|
||||
});
|
||||
break :stmt s;
|
||||
},
|
||||
}
|
||||
},
|
||||
.s_class => |st| stmt: {
|
||||
|
||||
// Strip the "export" keyword
|
||||
if (!st.is_export) {
|
||||
break :stmt stmt;
|
||||
}
|
||||
|
||||
// Export as CommonJS
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = p.symbols.items[st.class.class_name.?.ref.?.inner_index].original_name,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(st.class.class_name.?.ref.?, stmt.loc),
|
||||
});
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_function => |st| stmt: {
|
||||
// Strip the "export" keyword
|
||||
if (!st.func.flags.contains(.is_export)) break :stmt stmt;
|
||||
|
||||
st.func.flags.remove(.is_export);
|
||||
|
||||
try ctx.visitRefToExport(
|
||||
p,
|
||||
st.func.name.?.ref.?,
|
||||
null,
|
||||
stmt.loc,
|
||||
false,
|
||||
);
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_clause => |st| {
|
||||
for (st.items) |item| {
|
||||
const ref = item.name.ref.?;
|
||||
try ctx.visitRefToExport(p, ref, item.alias, item.name.loc, false);
|
||||
}
|
||||
|
||||
return; // do not emit a statement here
|
||||
},
|
||||
.s_export_from => |st| {
|
||||
const namespace_ref = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
st.items,
|
||||
stmt.loc,
|
||||
null,
|
||||
stmt.loc,
|
||||
);
|
||||
for (st.items) |*item| {
|
||||
const ref = item.name.ref.?;
|
||||
const symbol = &p.symbols.items[ref.innerIndex()];
|
||||
if (symbol.namespace_alias == null) {
|
||||
symbol.namespace_alias = .{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = item.original_name,
|
||||
.import_record_index = st.import_record_index,
|
||||
};
|
||||
}
|
||||
try ctx.visitRefToExport(
|
||||
p,
|
||||
ref,
|
||||
item.alias,
|
||||
item.name.loc,
|
||||
!ctx.is_in_node_modules, // live binding when this may be replaced
|
||||
);
|
||||
|
||||
// imports and export statements have their alias +
|
||||
// original_name swapped. this is likely a design bug in
|
||||
// the parser but since everything uses these
|
||||
// assumptions, this hack is simpler than making it
|
||||
// proper
|
||||
const alias = item.alias;
|
||||
item.alias = item.original_name;
|
||||
item.original_name = alias;
|
||||
}
|
||||
return;
|
||||
},
|
||||
.s_export_star => |st| {
|
||||
const namespace_ref = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
&.{},
|
||||
stmt.loc,
|
||||
null,
|
||||
stmt.loc,
|
||||
);
|
||||
|
||||
if (st.alias) |alias| {
|
||||
// 'export * as ns from' creates one named property.
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = alias.original_name }, stmt.loc),
|
||||
.value = Expr.initIdentifier(namespace_ref, stmt.loc),
|
||||
});
|
||||
} else {
|
||||
// 'export * from' creates a spread, hoisted at the top.
|
||||
try ctx.export_star_props.append(p.allocator, .{
|
||||
.kind = .spread,
|
||||
.value = Expr.initIdentifier(namespace_ref, stmt.loc),
|
||||
});
|
||||
}
|
||||
return;
|
||||
},
|
||||
// De-duplicate import statements. It is okay to disregard
|
||||
// named/default imports here as we always rewrite them as
|
||||
// full qualified property accesses (needed for live-bindings)
|
||||
.s_import => |st| {
|
||||
_ = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
st.items,
|
||||
st.star_name_loc,
|
||||
st.default_name,
|
||||
stmt.loc,
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
try ctx.stmts.append(p.allocator, new_stmt);
|
||||
}
|
||||
|
||||
/// Deduplicates imports, returning a previously used Ref if present.
|
||||
fn deduplicatedImport(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: anytype,
|
||||
import_record_index: u32,
|
||||
namespace_ref: Ref,
|
||||
items: []js_ast.ClauseItem,
|
||||
star_name_loc: ?logger.Loc,
|
||||
default_name: ?js_ast.LocRef,
|
||||
loc: logger.Loc,
|
||||
) !Ref {
|
||||
const ir = &p.import_records.items[import_record_index];
|
||||
const gop = try ctx.imports_seen.getOrPut(p.allocator, ir.path.text);
|
||||
if (gop.found_existing) {
|
||||
// Disable this one since an older record is getting used. It isn't
|
||||
// practical to delete this import record entry since an import or
|
||||
// require expression can exist.
|
||||
ir.is_unused = true;
|
||||
|
||||
const stmt = ctx.stmts.items[gop.value_ptr.stmt_index].data.s_import;
|
||||
if (items.len > 0) {
|
||||
if (stmt.items.len == 0) {
|
||||
stmt.items = items;
|
||||
} else {
|
||||
stmt.items = try std.mem.concat(p.allocator, js_ast.ClauseItem, &.{ stmt.items, items });
|
||||
}
|
||||
}
|
||||
if (namespace_ref.isValid()) {
|
||||
if (!stmt.namespace_ref.isValid()) {
|
||||
stmt.namespace_ref = namespace_ref;
|
||||
return namespace_ref;
|
||||
} else {
|
||||
// Erase this namespace ref, but since it may be used in
|
||||
// existing AST trees, a link must be established.
|
||||
const symbol = &p.symbols.items[namespace_ref.innerIndex()];
|
||||
symbol.use_count_estimate = 0;
|
||||
symbol.link = stmt.namespace_ref;
|
||||
if (@hasField(@typeInfo(@TypeOf(p)).pointer.child, "symbol_uses")) {
|
||||
_ = p.symbol_uses.swapRemove(namespace_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stmt.star_name_loc == null) if (star_name_loc) |stl| {
|
||||
stmt.star_name_loc = stl;
|
||||
};
|
||||
if (stmt.default_name == null) if (default_name) |dn| {
|
||||
stmt.default_name = dn;
|
||||
};
|
||||
return stmt.namespace_ref;
|
||||
}
|
||||
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.Import, .{
|
||||
.import_record_index = import_record_index,
|
||||
.is_single_line = true,
|
||||
.default_name = default_name,
|
||||
.items = items,
|
||||
.namespace_ref = namespace_ref,
|
||||
.star_name_loc = star_name_loc,
|
||||
}, loc));
|
||||
|
||||
gop.value_ptr.* = .{ .stmt_index = @intCast(ctx.stmts.items.len - 1) };
|
||||
return namespace_ref;
|
||||
}
|
||||
|
||||
fn visitBindingToExport(ctx: *ConvertESMExportsForHmr, p: anytype, binding: Binding) !void {
|
||||
switch (binding.data) {
|
||||
.b_missing => {},
|
||||
.b_identifier => |id| {
|
||||
try ctx.visitRefToExport(p, id.ref, null, binding.loc, false);
|
||||
},
|
||||
.b_array => |array| {
|
||||
for (array.items) |item| {
|
||||
try ctx.visitBindingToExport(p, item.binding);
|
||||
}
|
||||
},
|
||||
.b_object => |object| {
|
||||
for (object.properties) |item| {
|
||||
try ctx.visitBindingToExport(p, item.value);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visitRefToExport(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: anytype,
|
||||
ref: Ref,
|
||||
export_symbol_name: ?[]const u8,
|
||||
loc: logger.Loc,
|
||||
is_live_binding_source: bool,
|
||||
) !void {
|
||||
const symbol = p.symbols.items[ref.inner_index];
|
||||
const id = if (symbol.kind == .import)
|
||||
Expr.init(E.ImportIdentifier, .{ .ref = ref }, loc)
|
||||
else
|
||||
Expr.initIdentifier(ref, loc);
|
||||
if (is_live_binding_source or (symbol.kind == .import and !ctx.is_in_node_modules) or symbol.has_been_assigned_to) {
|
||||
// TODO (2024-11-24) instead of requiring getters for live-bindings,
|
||||
// a callback propagation system should be considered. mostly
|
||||
// because here, these might not even be live bindings, and
|
||||
// re-exports are so, so common.
|
||||
//
|
||||
// update(2025-03-05): HMRModule in ts now contains an exhaustive map
|
||||
// of importers. For local live bindings, these can just remember to
|
||||
// mutate the field in the exports object. Re-exports can just be
|
||||
// encoded into the module format, propagated in `replaceModules`
|
||||
const key = Expr.init(E.String, .{
|
||||
.data = export_symbol_name orelse symbol.original_name,
|
||||
}, loc);
|
||||
|
||||
// This is technically incorrect in that we've marked this as a
|
||||
// top level symbol. but all we care about is preventing name
|
||||
// collisions, not necessarily the best minificaiton (dev only)
|
||||
const arg1 = p.generateTempRef(symbol.original_name);
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = arg1, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, arg1, .{ .count_estimate = 1 });
|
||||
try p.current_scope.generated.push(p.allocator, arg1);
|
||||
|
||||
// 'get abc() { return abc }'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.kind = .get,
|
||||
.key = key,
|
||||
.value = Expr.init(E.Function, .{ .func = .{
|
||||
.body = .{
|
||||
.stmts = try p.allocator.dupe(Stmt, &.{
|
||||
Stmt.alloc(S.Return, .{ .value = id }, loc),
|
||||
}),
|
||||
.loc = loc,
|
||||
},
|
||||
} }, loc),
|
||||
});
|
||||
// no setter is added since live bindings are read-only
|
||||
} else {
|
||||
// 'abc,'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = export_symbol_name orelse symbol.original_name,
|
||||
}, loc),
|
||||
.value = id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(ctx: *ConvertESMExportsForHmr, p: anytype, all_parts: []js_ast.Part) !void {
|
||||
if (ctx.export_star_props.items.len > 0) {
|
||||
if (ctx.export_props.items.len == 0) {
|
||||
ctx.export_props = ctx.export_star_props;
|
||||
} else {
|
||||
const export_star_len = ctx.export_star_props.items.len;
|
||||
try ctx.export_props.ensureUnusedCapacity(p.allocator, export_star_len);
|
||||
const len = ctx.export_props.items.len;
|
||||
ctx.export_props.items.len += export_star_len;
|
||||
bun.copy(G.Property, ctx.export_props.items[export_star_len..], ctx.export_props.items[0..len]);
|
||||
@memcpy(ctx.export_props.items[0..export_star_len], ctx.export_star_props.items);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.export_props.items.len > 0) {
|
||||
const obj = Expr.init(E.Object, .{
|
||||
.properties = G.Property.List.fromList(ctx.export_props),
|
||||
}, logger.Loc.Empty);
|
||||
|
||||
// `hmr.exports = ...`
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(
|
||||
Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.hmr_api_ref, logger.Loc.Empty),
|
||||
.name = "exports",
|
||||
.name_loc = logger.Loc.Empty,
|
||||
}, logger.Loc.Empty),
|
||||
obj,
|
||||
),
|
||||
}, logger.Loc.Empty));
|
||||
|
||||
// mark a dependency on module_ref so it is renamed
|
||||
try ctx.last_part.symbol_uses.put(p.allocator, p.module_ref, .{ .count_estimate = 1 });
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = p.module_ref, .is_top_level = true });
|
||||
}
|
||||
|
||||
if (p.options.features.react_fast_refresh and p.react_refresh.register_used) {
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.init(E.Call, .{
|
||||
.target = Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.hmr_api_ref, .Empty),
|
||||
.name = "reactRefreshAccept",
|
||||
.name_loc = .Empty,
|
||||
}, .Empty),
|
||||
.args = .init(&.{}),
|
||||
}, .Empty),
|
||||
}, .Empty));
|
||||
}
|
||||
|
||||
// Merge all part metadata into the first part.
|
||||
for (all_parts[0 .. all_parts.len - 1]) |*part| {
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, part.declared_symbols);
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, part.import_record_indices.slice());
|
||||
for (part.symbol_uses.keys(), part.symbol_uses.values()) |k, v| {
|
||||
const gop = try ctx.last_part.symbol_uses.getOrPut(p.allocator, k);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = v;
|
||||
} else {
|
||||
gop.value_ptr.count_estimate += v.count_estimate;
|
||||
}
|
||||
}
|
||||
part.stmts = &.{};
|
||||
part.declared_symbols.entries.len = 0;
|
||||
part.tag = .dead_due_to_inlining;
|
||||
part.dependencies.clearRetainingCapacity();
|
||||
try part.dependencies.push(p.allocator, .{
|
||||
.part_index = @intCast(all_parts.len - 1),
|
||||
.source_index = p.source.index,
|
||||
});
|
||||
}
|
||||
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, p.import_records_for_current_part.items);
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, p.declared_symbols);
|
||||
|
||||
ctx.last_part.stmts = ctx.stmts.items;
|
||||
ctx.last_part.tag = .none;
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const logger = bun.logger;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const B = js_ast.B;
|
||||
const Binding = js_ast.Binding;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const Stmt = js_ast.Stmt;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Decl = G.Decl;
|
||||
const Property = G.Property;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const ConvertESMExportsForHmr = js_parser.ConvertESMExportsForHmr;
|
||||
const ReactRefresh = js_parser.ReactRefresh;
|
||||
const Ref = js_parser.Ref;
|
||||
const options = js_parser.options;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
@@ -19,7 +19,7 @@ pub fn clone(this: Expr, allocator: std.mem.Allocator) !Expr {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deepClone(this: Expr, allocator: std.mem.Allocator) OOM!Expr {
|
||||
pub fn deepClone(this: Expr, allocator: std.mem.Allocator) anyerror!Expr {
|
||||
return .{
|
||||
.loc = this.loc,
|
||||
.data = try this.data.deepClone(allocator),
|
||||
|
||||
@@ -1,530 +0,0 @@
|
||||
stmts: []Stmt = &.{},
|
||||
kept_import_equals: bool = false,
|
||||
removed_import_equals: bool = false,
|
||||
|
||||
pub fn scan(
|
||||
comptime P: type,
|
||||
p: *P,
|
||||
stmts: []Stmt,
|
||||
will_transform_to_common_js: bool,
|
||||
comptime hot_module_reloading_transformations: bool,
|
||||
hot_module_reloading_context: if (hot_module_reloading_transformations) *ConvertESMExportsForHmr else void,
|
||||
) !ImportScanner {
|
||||
var scanner = ImportScanner{};
|
||||
var stmts_end: usize = 0;
|
||||
const allocator = p.allocator;
|
||||
const is_typescript_enabled: bool = comptime P.parser_features.typescript;
|
||||
|
||||
for (stmts) |_stmt| {
|
||||
var stmt = _stmt; // copy
|
||||
switch (stmt.data) {
|
||||
.s_import => |import_ptr| {
|
||||
var st = import_ptr.*;
|
||||
defer import_ptr.* = st;
|
||||
|
||||
const record: *ImportRecord = &p.import_records.items[st.import_record_index];
|
||||
|
||||
if (record.path.isMacro()) {
|
||||
record.is_unused = true;
|
||||
record.path.is_disabled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The official TypeScript compiler always removes unused imported
|
||||
// symbols. However, we deliberately deviate from the official
|
||||
// TypeScript compiler's behavior doing this in a specific scenario:
|
||||
// we are not bundling, symbol renaming is off, and the tsconfig.json
|
||||
// "importsNotUsedAsValues" setting is present and is not set to
|
||||
// "remove".
|
||||
//
|
||||
// This exists to support the use case of compiling partial modules for
|
||||
// compile-to-JavaScript languages such as Svelte. These languages try
|
||||
// to reference imports in ways that are impossible for esbuild to know
|
||||
// about when esbuild is only given a partial module to compile. Here
|
||||
// is an example of some Svelte code that might use esbuild to convert
|
||||
// TypeScript to JavaScript:
|
||||
//
|
||||
// <script lang="ts">
|
||||
// import Counter from './Counter.svelte';
|
||||
// export let name: string = 'world';
|
||||
// </script>
|
||||
// <main>
|
||||
// <h1>Hello {name}!</h1>
|
||||
// <Counter />
|
||||
// </main>
|
||||
//
|
||||
// Tools that use esbuild to compile TypeScript code inside a Svelte
|
||||
// file like this only give esbuild the contents of the <script> tag.
|
||||
// These tools work around this missing import problem when using the
|
||||
// official TypeScript compiler by hacking the TypeScript AST to
|
||||
// remove the "unused import" flags. This isn't possible in esbuild
|
||||
// because esbuild deliberately does not expose an AST manipulation
|
||||
// API for performance reasons.
|
||||
//
|
||||
// We deviate from the TypeScript compiler's behavior in this specific
|
||||
// case because doing so is useful for these compile-to-JavaScript
|
||||
// languages and is benign in other cases. The rationale is as follows:
|
||||
//
|
||||
// * If "importsNotUsedAsValues" is absent or set to "remove", then
|
||||
// we don't know if these imports are values or types. It's not
|
||||
// safe to keep them because if they are types, the missing imports
|
||||
// will cause run-time failures because there will be no matching
|
||||
// exports. It's only safe keep imports if "importsNotUsedAsValues"
|
||||
// is set to "preserve" or "error" because then we can assume that
|
||||
// none of the imports are types (since the TypeScript compiler
|
||||
// would generate an error in that case).
|
||||
//
|
||||
// * If we're bundling, then we know we aren't being used to compile
|
||||
// a partial module. The parser is seeing the entire code for the
|
||||
// module so it's safe to remove unused imports. And also we don't
|
||||
// want the linker to generate errors about missing imports if the
|
||||
// imported file is also in the bundle.
|
||||
//
|
||||
// * If identifier minification is enabled, then using esbuild as a
|
||||
// partial-module transform library wouldn't work anyway because
|
||||
// the names wouldn't match. And that means we're minifying so the
|
||||
// user is expecting the output to be as small as possible. So we
|
||||
// should omit unused imports.
|
||||
//
|
||||
var did_remove_star_loc = false;
|
||||
const keep_unused_imports = !p.options.features.trim_unused_imports;
|
||||
// TypeScript always trims unused imports. This is important for
|
||||
// correctness since some imports might be fake (only in the type
|
||||
// system and used for type-only imports).
|
||||
if (!keep_unused_imports) {
|
||||
var found_imports = false;
|
||||
var is_unused_in_typescript = true;
|
||||
|
||||
if (st.default_name) |default_name| {
|
||||
found_imports = true;
|
||||
const symbol = p.symbols.items[default_name.ref.?.innerIndex()];
|
||||
|
||||
// TypeScript has a separate definition of unused
|
||||
if (is_typescript_enabled and p.ts_use_counts.items[default_name.ref.?.innerIndex()] != 0) {
|
||||
is_unused_in_typescript = false;
|
||||
}
|
||||
|
||||
// Remove the symbol if it's never used outside a dead code region
|
||||
if (symbol.use_count_estimate == 0) {
|
||||
st.default_name = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the star import if it's unused
|
||||
if (st.star_name_loc) |_| {
|
||||
found_imports = true;
|
||||
const symbol = p.symbols.items[st.namespace_ref.innerIndex()];
|
||||
|
||||
// TypeScript has a separate definition of unused
|
||||
if (is_typescript_enabled and p.ts_use_counts.items[st.namespace_ref.innerIndex()] != 0) {
|
||||
is_unused_in_typescript = false;
|
||||
}
|
||||
|
||||
// Remove the symbol if it's never used outside a dead code region
|
||||
if (symbol.use_count_estimate == 0) {
|
||||
// Make sure we don't remove this if it was used for a property
|
||||
// access while bundling
|
||||
var has_any = false;
|
||||
|
||||
if (p.import_items_for_namespace.get(st.namespace_ref)) |entry| {
|
||||
if (entry.count() > 0) {
|
||||
has_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_any) {
|
||||
st.star_name_loc = null;
|
||||
did_remove_star_loc = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove items if they are unused
|
||||
if (st.items.len > 0) {
|
||||
found_imports = true;
|
||||
var items_end: usize = 0;
|
||||
for (st.items) |item| {
|
||||
const ref = item.name.ref.?;
|
||||
const symbol: Symbol = p.symbols.items[ref.innerIndex()];
|
||||
|
||||
// TypeScript has a separate definition of unused
|
||||
if (is_typescript_enabled and p.ts_use_counts.items[ref.innerIndex()] != 0) {
|
||||
is_unused_in_typescript = false;
|
||||
}
|
||||
|
||||
// Remove the symbol if it's never used outside a dead code region
|
||||
if (symbol.use_count_estimate != 0) {
|
||||
st.items[items_end] = item;
|
||||
items_end += 1;
|
||||
}
|
||||
}
|
||||
|
||||
st.items = st.items[0..items_end];
|
||||
}
|
||||
|
||||
// -- Original Comment --
|
||||
// Omit this statement if we're parsing TypeScript and all imports are
|
||||
// unused. Note that this is distinct from the case where there were
|
||||
// no imports at all (e.g. "import 'foo'"). In that case we want to keep
|
||||
// the statement because the user is clearly trying to import the module
|
||||
// for side effects.
|
||||
//
|
||||
// This culling is important for correctness when parsing TypeScript
|
||||
// because a) the TypeScript compiler does this and we want to match it
|
||||
// and b) this may be a fake module that only exists in the type system
|
||||
// and doesn't actually exist in reality.
|
||||
//
|
||||
// We do not want to do this culling in JavaScript though because the
|
||||
// module may have side effects even if all imports are unused.
|
||||
// -- Original Comment --
|
||||
|
||||
// jarred: I think, in this project, we want this behavior, even in JavaScript.
|
||||
// I think this would be a big performance improvement.
|
||||
// The less you import, the less code you transpile.
|
||||
// Side-effect imports are nearly always done through identifier-less imports
|
||||
// e.g. `import 'fancy-stylesheet-thing/style.css';`
|
||||
// This is a breaking change though. We can make it an option with some guardrail
|
||||
// so maybe if it errors, it shows a suggestion "retry without trimming unused imports"
|
||||
if ((is_typescript_enabled and found_imports and is_unused_in_typescript and !p.options.preserve_unused_imports_ts) or
|
||||
(!is_typescript_enabled and p.options.features.trim_unused_imports and found_imports and st.star_name_loc == null and st.items.len == 0 and st.default_name == null))
|
||||
{
|
||||
// internal imports are presumed to be always used
|
||||
// require statements cannot be stripped
|
||||
if (!record.is_internal and !record.was_originally_require) {
|
||||
record.is_unused = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const namespace_ref = st.namespace_ref;
|
||||
const convert_star_to_clause = !p.options.bundle and (p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0);
|
||||
|
||||
if (convert_star_to_clause and !keep_unused_imports) {
|
||||
st.star_name_loc = null;
|
||||
}
|
||||
|
||||
record.contains_default_alias = record.contains_default_alias or st.default_name != null;
|
||||
|
||||
const existing_items: ImportItemForNamespaceMap = p.import_items_for_namespace.get(namespace_ref) orelse
|
||||
ImportItemForNamespaceMap.init(allocator);
|
||||
|
||||
if (p.options.bundle) {
|
||||
if (st.star_name_loc != null and existing_items.count() > 0) {
|
||||
const sorted = try allocator.alloc(string, existing_items.count());
|
||||
defer allocator.free(sorted);
|
||||
for (sorted, existing_items.keys()) |*result, alias| {
|
||||
result.* = alias;
|
||||
}
|
||||
strings.sortDesc(sorted);
|
||||
p.named_imports.ensureUnusedCapacity(p.allocator, sorted.len) catch bun.outOfMemory();
|
||||
|
||||
// Create named imports for these property accesses. This will
|
||||
// cause missing imports to generate useful warnings.
|
||||
//
|
||||
// It will also improve bundling efficiency for internal imports
|
||||
// by still converting property accesses off the namespace into
|
||||
// bare identifiers even if the namespace is still needed.
|
||||
for (sorted) |alias| {
|
||||
const item = existing_items.get(alias).?;
|
||||
p.named_imports.put(
|
||||
p.allocator,
|
||||
item.ref.?,
|
||||
js_ast.NamedImport{
|
||||
.alias = alias,
|
||||
.alias_loc = item.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
) catch bun.outOfMemory();
|
||||
|
||||
const name: LocRef = item;
|
||||
const name_ref = name.ref.?;
|
||||
|
||||
// Make sure the printer prints this as a property access
|
||||
var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()];
|
||||
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = alias,
|
||||
.import_record_index = st.import_record_index,
|
||||
.was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name),
|
||||
};
|
||||
|
||||
// Also record these automatically-generated top-level namespace alias symbols
|
||||
p.declared_symbols.append(p.allocator, .{
|
||||
.ref = name_ref,
|
||||
.is_top_level = true,
|
||||
}) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
p.named_imports.ensureUnusedCapacity(
|
||||
p.allocator,
|
||||
st.items.len + @as(usize, @intFromBool(st.default_name != null)) + @as(usize, @intFromBool(st.star_name_loc != null)),
|
||||
) catch bun.outOfMemory();
|
||||
|
||||
if (st.star_name_loc) |loc| {
|
||||
record.contains_import_star = true;
|
||||
p.named_imports.putAssumeCapacity(
|
||||
namespace_ref,
|
||||
js_ast.NamedImport{
|
||||
.alias_is_star = true,
|
||||
.alias = "",
|
||||
.alias_loc = loc,
|
||||
.namespace_ref = Ref.None,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (st.default_name) |default| {
|
||||
record.contains_default_alias = true;
|
||||
p.named_imports.putAssumeCapacity(
|
||||
default.ref.?,
|
||||
.{
|
||||
.alias = "default",
|
||||
.alias_loc = default.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (st.items) |item| {
|
||||
const name: LocRef = item.name;
|
||||
const name_ref = name.ref.?;
|
||||
|
||||
p.named_imports.putAssumeCapacity(
|
||||
name_ref,
|
||||
js_ast.NamedImport{
|
||||
.alias = item.alias,
|
||||
.alias_loc = name.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// ESM requires live bindings
|
||||
// CommonJS does not require live bindings
|
||||
// We load ESM in browsers & in Bun.js
|
||||
// We have to simulate live bindings for cases where the code is bundled
|
||||
// We do not know at this stage whether or not the import statement is bundled
|
||||
// This keeps track of the `namespace_alias` incase, at printing time, we determine that we should print it with the namespace
|
||||
for (st.items) |item| {
|
||||
record.contains_default_alias = record.contains_default_alias or strings.eqlComptime(item.alias, "default");
|
||||
|
||||
const name: LocRef = item.name;
|
||||
const name_ref = name.ref.?;
|
||||
|
||||
try p.named_imports.put(p.allocator, name_ref, js_ast.NamedImport{
|
||||
.alias = item.alias,
|
||||
.alias_loc = name.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
});
|
||||
|
||||
// Make sure the printer prints this as a property access
|
||||
var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()];
|
||||
if (record.contains_import_star or st.star_name_loc != null)
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = item.alias,
|
||||
.import_record_index = st.import_record_index,
|
||||
.was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name),
|
||||
};
|
||||
}
|
||||
|
||||
if (record.was_originally_require) {
|
||||
var symbol = &p.symbols.items[namespace_ref.innerIndex()];
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = "",
|
||||
.import_record_index = st.import_record_index,
|
||||
.was_originally_property_access = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try p.import_records_for_current_part.append(allocator, st.import_record_index);
|
||||
|
||||
record.contains_import_star = record.contains_import_star or st.star_name_loc != null;
|
||||
record.contains_default_alias = record.contains_default_alias or st.default_name != null;
|
||||
|
||||
for (st.items) |*item| {
|
||||
record.contains_default_alias = record.contains_default_alias or strings.eqlComptime(item.alias, "default");
|
||||
record.contains_es_module_alias = record.contains_es_module_alias or strings.eqlComptime(item.alias, "__esModule");
|
||||
}
|
||||
},
|
||||
|
||||
.s_function => |st| {
|
||||
if (st.func.flags.contains(.is_export)) {
|
||||
if (st.func.name) |name| {
|
||||
const original_name = p.symbols.items[name.ref.?.innerIndex()].original_name;
|
||||
try p.recordExport(name.loc, original_name, name.ref.?);
|
||||
} else {
|
||||
try p.log.addRangeError(p.source, logger.Range{ .loc = st.func.open_parens_loc, .len = 2 }, "Exported functions must have a name");
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_class => |st| {
|
||||
if (st.is_export) {
|
||||
if (st.class.class_name) |name| {
|
||||
try p.recordExport(name.loc, p.symbols.items[name.ref.?.innerIndex()].original_name, name.ref.?);
|
||||
} else {
|
||||
try p.log.addRangeError(p.source, logger.Range{ .loc = st.class.body_loc, .len = 0 }, "Exported classes must have a name");
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_local => |st| {
|
||||
if (st.is_export) {
|
||||
for (st.decls.slice()) |decl| {
|
||||
p.recordExportedBinding(decl.binding);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused import-equals statements, since those likely
|
||||
// correspond to types instead of values
|
||||
if (st.was_ts_import_equals and !st.is_export and st.decls.len > 0) {
|
||||
var decl = st.decls.ptr[0];
|
||||
|
||||
// Skip to the underlying reference
|
||||
var value = decl.value;
|
||||
if (decl.value != null) {
|
||||
while (true) {
|
||||
if (@as(Expr.Tag, value.?.data) == .e_dot) {
|
||||
value = value.?.data.e_dot.target;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is this an identifier reference and not a require() call?
|
||||
if (value) |val| {
|
||||
if (@as(Expr.Tag, val.data) == .e_identifier) {
|
||||
// Is this import statement unused?
|
||||
if (@as(Binding.Tag, decl.binding.data) == .b_identifier and p.symbols.items[decl.binding.data.b_identifier.ref.innerIndex()].use_count_estimate == 0) {
|
||||
p.ignoreUsage(val.data.e_identifier.ref);
|
||||
|
||||
scanner.removed_import_equals = true;
|
||||
continue;
|
||||
} else {
|
||||
scanner.kept_import_equals = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_export_default => |st| {
|
||||
// This is defer'd so that we still record export default for identifiers
|
||||
defer {
|
||||
if (st.default_name.ref) |ref| {
|
||||
p.recordExport(st.default_name.loc, "default", ref) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite this export to be:
|
||||
// exports.default =
|
||||
// But only if it's anonymous
|
||||
if (!hot_module_reloading_transformations and will_transform_to_common_js and P != bun.bundle_v2.AstBuilder) {
|
||||
const expr = st.value.toExpr();
|
||||
var export_default_args = try p.allocator.alloc(Expr, 2);
|
||||
export_default_args[0] = p.@"module.exports"(expr.loc);
|
||||
export_default_args[1] = expr;
|
||||
stmt = p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc);
|
||||
}
|
||||
},
|
||||
.s_export_clause => |st| {
|
||||
for (st.items) |item| {
|
||||
try p.recordExport(item.alias_loc, item.alias, item.name.ref.?);
|
||||
}
|
||||
},
|
||||
.s_export_star => |st| {
|
||||
try p.import_records_for_current_part.append(allocator, st.import_record_index);
|
||||
|
||||
if (st.alias) |alias| {
|
||||
// "export * as ns from 'path'"
|
||||
try p.named_imports.put(p.allocator, st.namespace_ref, js_ast.NamedImport{
|
||||
.alias = null,
|
||||
.alias_is_star = true,
|
||||
.alias_loc = alias.loc,
|
||||
.namespace_ref = Ref.None,
|
||||
.import_record_index = st.import_record_index,
|
||||
.is_exported = true,
|
||||
});
|
||||
try p.recordExport(alias.loc, alias.original_name, st.namespace_ref);
|
||||
var record = &p.import_records.items[st.import_record_index];
|
||||
record.contains_import_star = true;
|
||||
} else {
|
||||
// "export * from 'path'"
|
||||
try p.export_star_import_records.append(allocator, st.import_record_index);
|
||||
}
|
||||
},
|
||||
.s_export_from => |st| {
|
||||
try p.import_records_for_current_part.append(allocator, st.import_record_index);
|
||||
p.named_imports.ensureUnusedCapacity(p.allocator, st.items.len) catch unreachable;
|
||||
for (st.items) |item| {
|
||||
const ref = item.name.ref orelse p.panic("Expected export from item to have a name {any}", .{st});
|
||||
// Note that the imported alias is not item.Alias, which is the
|
||||
// exported alias. This is somewhat confusing because each
|
||||
// SExportFrom statement is basically SImport + SExportClause in one.
|
||||
try p.named_imports.put(p.allocator, ref, js_ast.NamedImport{
|
||||
.alias_is_star = false,
|
||||
.alias = item.original_name,
|
||||
.alias_loc = item.name.loc,
|
||||
.namespace_ref = st.namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
.is_exported = true,
|
||||
});
|
||||
try p.recordExport(item.name.loc, item.alias, ref);
|
||||
|
||||
var record = &p.import_records.items[st.import_record_index];
|
||||
if (strings.eqlComptime(item.original_name, "default")) {
|
||||
record.contains_default_alias = true;
|
||||
} else if (strings.eqlComptime(item.original_name, "__esModule")) {
|
||||
record.contains_es_module_alias = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (hot_module_reloading_transformations) {
|
||||
try hot_module_reloading_context.convertStmt(p, stmt);
|
||||
} else {
|
||||
stmts[stmts_end] = stmt;
|
||||
stmts_end += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hot_module_reloading_transformations)
|
||||
scanner.stmts = stmts[0..stmts_end];
|
||||
|
||||
return scanner;
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const ImportRecord = bun.ImportRecord;
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const Binding = js_ast.Binding;
|
||||
const Expr = js_ast.Expr;
|
||||
const G = js_ast.G;
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const Stmt = js_ast.Stmt;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const ConvertESMExportsForHmr = js_parser.ConvertESMExportsForHmr;
|
||||
const ImportItemForNamespaceMap = js_parser.ImportItemForNamespaceMap;
|
||||
const ImportScanner = js_parser.ImportScanner;
|
||||
const Ref = js_parser.Ref;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
const options = js_parser.options;
|
||||
@@ -1,210 +0,0 @@
|
||||
pub const KnownGlobal = enum {
|
||||
WeakSet,
|
||||
WeakMap,
|
||||
Date,
|
||||
Set,
|
||||
Map,
|
||||
Headers,
|
||||
Response,
|
||||
TextEncoder,
|
||||
TextDecoder,
|
||||
|
||||
pub const map = bun.ComptimeEnumMap(KnownGlobal);
|
||||
|
||||
pub noinline fn maybeMarkConstructorAsPure(noalias e: *E.New, symbols: []const Symbol) void {
|
||||
const id = if (e.target.data == .e_identifier) e.target.data.e_identifier.ref else return;
|
||||
const symbol = &symbols[id.innerIndex()];
|
||||
if (symbol.kind != .unbound)
|
||||
return;
|
||||
|
||||
const constructor = map.get(symbol.original_name) orelse return;
|
||||
|
||||
switch (constructor) {
|
||||
.WeakSet, .WeakMap => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new WeakSet()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].data) {
|
||||
.e_null, .e_undefined => {
|
||||
// "new WeakSet(null)" is pure
|
||||
// "new WeakSet(void 0)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
.e_array => |array| {
|
||||
if (array.items.len == 0) {
|
||||
// "new WeakSet([])" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
} else {
|
||||
// "new WeakSet([x])" is impure because an exception is thrown if "x" is not an object
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// "new WeakSet(x)" is impure because the iterator for "x" could have side effects
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
.Date => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Date()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].knownPrimitive()) {
|
||||
.null, .undefined, .boolean, .number, .string => {
|
||||
// "new Date('')" is pure
|
||||
// "new Date(0)" is pure
|
||||
// "new Date(null)" is pure
|
||||
// "new Date(true)" is pure
|
||||
// "new Date(false)" is pure
|
||||
// "new Date(undefined)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
else => {
|
||||
// "new Date(x)" is impure because the argument could be a string with side effects
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.Set => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Set()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].data) {
|
||||
.e_array, .e_null, .e_undefined => {
|
||||
// "new Set([a, b, c])" is pure
|
||||
// "new Set(null)" is pure
|
||||
// "new Set(void 0)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
else => {
|
||||
// "new Set(x)" is impure because the iterator for "x" could have side effects
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.Headers => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Headers()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
.Response => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Response()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].knownPrimitive()) {
|
||||
.null, .undefined, .boolean, .number, .string => {
|
||||
// "new Response('')" is pure
|
||||
// "new Response(0)" is pure
|
||||
// "new Response(null)" is pure
|
||||
// "new Response(true)" is pure
|
||||
// "new Response(false)" is pure
|
||||
// "new Response(undefined)" is pure
|
||||
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
else => {
|
||||
// "new Response(x)" is impure
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
.TextDecoder, .TextEncoder => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new TextEncoder()" is pure
|
||||
// "new TextDecoder()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We _could_ validate the encoding argument
|
||||
// But let's not bother
|
||||
},
|
||||
|
||||
.Map => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Map()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].data) {
|
||||
.e_null, .e_undefined => {
|
||||
// "new Map(null)" is pure
|
||||
// "new Map(void 0)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
.e_array => |array| {
|
||||
var all_items_are_arrays = true;
|
||||
for (array.items.slice()) |item| {
|
||||
if (item.data != .e_array) {
|
||||
all_items_are_arrays = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_items_are_arrays) {
|
||||
// "new Map([[a, b], [c, d]])" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// "new Map(x)" is impure because the iterator for "x" could have side effects
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const std = @import("std");
|
||||
const Map = std.AutoHashMapUnmanaged;
|
||||
@@ -22,7 +22,7 @@ pub fn NewStore(comptime types: []const type, comptime count: usize) type {
|
||||
|
||||
const backing_allocator = bun.default_allocator;
|
||||
|
||||
const log = Output.scoped(.Store, .hidden);
|
||||
const log = Output.scoped(.Store, true);
|
||||
|
||||
return struct {
|
||||
const Store = @This();
|
||||
|
||||
6666
src/ast/P.zig
6666
src/ast/P.zig
File diff suppressed because it is too large
Load Diff
1528
src/ast/Parser.zig
1528
src/ast/Parser.zig
File diff suppressed because it is too large
Load Diff
@@ -1,887 +0,0 @@
|
||||
pub const SideEffects = enum(u1) {
|
||||
could_have_side_effects,
|
||||
no_side_effects,
|
||||
|
||||
pub const Result = struct {
|
||||
side_effects: SideEffects,
|
||||
ok: bool = false,
|
||||
value: bool = false,
|
||||
};
|
||||
|
||||
pub fn canChangeStrictToLoose(lhs: Expr.Data, rhs: Expr.Data) bool {
|
||||
const left = lhs.knownPrimitive();
|
||||
const right = rhs.knownPrimitive();
|
||||
return left == right and left != .unknown and left != .mixed;
|
||||
}
|
||||
|
||||
pub fn simplifyBoolean(p: anytype, expr: Expr) Expr {
|
||||
if (!p.options.features.dead_code_elimination) return expr;
|
||||
|
||||
var result: Expr = expr;
|
||||
_simplifyBoolean(p, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn _simplifyBoolean(p: anytype, expr: *Expr) void {
|
||||
while (true) {
|
||||
switch (expr.data) {
|
||||
.e_unary => |e| {
|
||||
if (e.op == .un_not) {
|
||||
// "!!a" => "a"
|
||||
if (e.value.data == .e_unary and e.value.data.e_unary.op == .un_not) {
|
||||
expr.* = e.value.data.e_unary.value;
|
||||
continue;
|
||||
}
|
||||
|
||||
_simplifyBoolean(p, &e.value);
|
||||
}
|
||||
},
|
||||
.e_binary => |e| {
|
||||
switch (e.op) {
|
||||
.bin_logical_and => {
|
||||
const effects = SideEffects.toBoolean(p, e.right.data);
|
||||
if (effects.ok and effects.value and effects.side_effects == .no_side_effects) {
|
||||
// "if (anything && truthyNoSideEffects)" => "if (anything)"
|
||||
expr.* = e.left;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
.bin_logical_or => {
|
||||
const effects = SideEffects.toBoolean(p, e.right.data);
|
||||
if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) {
|
||||
// "if (anything || falsyNoSideEffects)" => "if (anything)"
|
||||
expr.* = e.left;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pub const toNumber = Expr.Data.toNumber;
|
||||
pub const typeof = Expr.Data.toTypeof;
|
||||
|
||||
pub fn isPrimitiveToReorder(data: Expr.Data) bool {
|
||||
return switch (data) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_string,
|
||||
.e_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_inlined_enum,
|
||||
.e_require_main,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn simplifyUnusedExpr(p: anytype, expr: Expr) ?Expr {
|
||||
if (!p.options.features.dead_code_elimination) return expr;
|
||||
switch (expr.data) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_missing,
|
||||
.e_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_string,
|
||||
.e_this,
|
||||
.e_reg_exp,
|
||||
.e_function,
|
||||
.e_arrow,
|
||||
.e_import_meta,
|
||||
.e_inlined_enum,
|
||||
=> return null,
|
||||
|
||||
.e_dot => |dot| {
|
||||
if (dot.can_be_removed_if_unused) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.e_identifier => |ident| {
|
||||
if (ident.must_keep_due_to_with_stmt) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
if (ident.can_be_removed_if_unused or p.symbols.items[ident.ref.innerIndex()].kind != .unbound) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.e_if => |ternary| {
|
||||
ternary.yes = simplifyUnusedExpr(p, ternary.yes) orelse ternary.yes.toEmpty();
|
||||
ternary.no = simplifyUnusedExpr(p, ternary.no) orelse ternary.no.toEmpty();
|
||||
|
||||
// "foo() ? 1 : 2" => "foo()"
|
||||
if (ternary.yes.isEmpty() and ternary.no.isEmpty()) {
|
||||
return simplifyUnusedExpr(p, ternary.test_);
|
||||
}
|
||||
|
||||
// "foo() ? 1 : bar()" => "foo() || bar()"
|
||||
if (ternary.yes.isEmpty()) {
|
||||
return Expr.joinWithLeftAssociativeOp(
|
||||
.bin_logical_or,
|
||||
ternary.test_,
|
||||
ternary.no,
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
|
||||
// "foo() ? bar() : 2" => "foo() && bar()"
|
||||
if (ternary.no.isEmpty()) {
|
||||
return Expr.joinWithLeftAssociativeOp(
|
||||
.bin_logical_and,
|
||||
ternary.test_,
|
||||
ternary.yes,
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
},
|
||||
.e_unary => |un| {
|
||||
// These operators must not have any type conversions that can execute code
|
||||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||||
switch (un.op) {
|
||||
.un_void, .un_not => {
|
||||
return simplifyUnusedExpr(p, un.value);
|
||||
},
|
||||
.un_typeof => {
|
||||
// "typeof x" must not be transformed into if "x" since doing so could
|
||||
// cause an exception to be thrown. Instead we can just remove it since
|
||||
// "typeof x" is special-cased in the standard to never throw.
|
||||
if (std.meta.activeTag(un.value.data) == .e_identifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return simplifyUnusedExpr(p, un.value);
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
inline .e_call, .e_new => |call| {
|
||||
// A call that has been marked "__PURE__" can be removed if all arguments
|
||||
// can be removed. The annotation causes us to ignore the target.
|
||||
if (call.can_be_unwrapped_if_unused != .never) {
|
||||
if (call.args.len > 0) {
|
||||
const joined = Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, comptime simplifyUnusedExpr, p.allocator);
|
||||
if (joined != null and call.can_be_unwrapped_if_unused == .if_unused_and_toString_safe) {
|
||||
@branchHint(.unlikely);
|
||||
// For now, only support this for 1 argument.
|
||||
if (joined.?.data.isSafeToString()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return joined;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.e_binary => |bin| {
|
||||
switch (bin.op) {
|
||||
// These operators must not have any type conversions that can execute code
|
||||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> return simplifyUnusedBinaryCommaExpr(p, expr),
|
||||
|
||||
// We can simplify "==" and "!=" even though they can call "toString" and/or
|
||||
// "valueOf" if we can statically determine that the types of both sides are
|
||||
// primitives. In that case there won't be any chance for user-defined
|
||||
// "toString" and/or "valueOf" to be called.
|
||||
.bin_loose_eq,
|
||||
.bin_loose_ne,
|
||||
=> {
|
||||
if (isPrimitiveWithSideEffects(bin.left.data) and isPrimitiveWithSideEffects(bin.right.data)) {
|
||||
return Expr.joinWithComma(
|
||||
simplifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(),
|
||||
simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(),
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
// If one side is a number, the number can be printed as
|
||||
// `0` since the result being unused doesnt matter, we
|
||||
// only care to invoke the coercion.
|
||||
if (bin.left.data == .e_number) {
|
||||
bin.left.data = .{ .e_number = .{ .value = 0.0 } };
|
||||
} else if (bin.right.data == .e_number) {
|
||||
bin.right.data = .{ .e_number = .{ .value = 0.0 } };
|
||||
}
|
||||
},
|
||||
|
||||
.bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => {
|
||||
bin.right = simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty();
|
||||
// Preserve short-circuit behavior: the left expression is only unused if
|
||||
// the right expression can be completely removed. Otherwise, the left
|
||||
// expression is important for the branch.
|
||||
|
||||
if (bin.right.isEmpty())
|
||||
return simplifyUnusedExpr(p, bin.left);
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
.e_object => {
|
||||
// Objects with "..." spread expressions can't be unwrapped because the
|
||||
// "..." triggers code evaluation via getters. In that case, just trim
|
||||
// the other items instead and leave the object expression there.
|
||||
var properties_slice = expr.data.e_object.properties.slice();
|
||||
var end: usize = 0;
|
||||
for (properties_slice) |spread| {
|
||||
end = 0;
|
||||
if (spread.kind == .spread) {
|
||||
// Spread properties must always be evaluated
|
||||
for (properties_slice) |prop_| {
|
||||
var prop = prop_;
|
||||
if (prop_.kind != .spread) {
|
||||
const value = simplifyUnusedExpr(p, prop.value.?);
|
||||
if (value != null) {
|
||||
prop.value = value;
|
||||
} else if (!prop.flags.contains(.is_computed)) {
|
||||
continue;
|
||||
} else {
|
||||
prop.value = p.newExpr(E.Number{ .value = 0.0 }, prop.value.?.loc);
|
||||
}
|
||||
}
|
||||
|
||||
properties_slice[end] = prop_;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
properties_slice = properties_slice[0..end];
|
||||
expr.data.e_object.properties = G.Property.List.init(properties_slice);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
var result = Expr.init(E.Missing, E.Missing{}, expr.loc);
|
||||
|
||||
// Otherwise, the object can be completely removed. We only need to keep any
|
||||
// object properties with side effects. Apply this simplification recursively.
|
||||
for (properties_slice) |prop| {
|
||||
if (prop.flags.contains(.is_computed)) {
|
||||
// Make sure "ToString" is still evaluated on the key
|
||||
result = result.joinWithComma(
|
||||
p.newExpr(
|
||||
E.Binary{
|
||||
.op = .bin_add,
|
||||
.left = prop.key.?,
|
||||
.right = p.newExpr(E.String{}, prop.key.?.loc),
|
||||
},
|
||||
prop.key.?.loc,
|
||||
),
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
result = result.joinWithComma(
|
||||
simplifyUnusedExpr(p, prop.value.?) orelse prop.value.?.toEmpty(),
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
.e_array => {
|
||||
var items = expr.data.e_array.items.slice();
|
||||
|
||||
for (items) |item| {
|
||||
if (item.data == .e_spread) {
|
||||
var end: usize = 0;
|
||||
for (items) |item__| {
|
||||
const item_ = item__;
|
||||
if (item_.data != .e_missing) {
|
||||
items[end] = item_;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
expr.data.e_array.items = ExprNodeList.init(items[0..end]);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the array can be completely removed. We only need to keep any
|
||||
// array items with side effects. Apply this simplification recursively.
|
||||
return Expr.joinAllWithCommaCallback(
|
||||
items,
|
||||
@TypeOf(p),
|
||||
p,
|
||||
comptime simplifyUnusedExpr,
|
||||
p.allocator,
|
||||
);
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
pub const BinaryExpressionSimplifyVisitor = struct {
|
||||
bin: *E.Binary,
|
||||
};
|
||||
|
||||
///
|
||||
fn simplifyUnusedBinaryCommaExpr(p: anytype, expr: Expr) ?Expr {
|
||||
if (Environment.allow_assert) {
|
||||
assert(expr.data == .e_binary);
|
||||
assert(switch (expr.data.e_binary.op) {
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> true,
|
||||
else => false,
|
||||
});
|
||||
}
|
||||
const stack: *std.ArrayList(BinaryExpressionSimplifyVisitor) = &p.binary_expression_simplify_stack;
|
||||
const stack_bottom = stack.items.len;
|
||||
defer stack.shrinkRetainingCapacity(stack_bottom);
|
||||
|
||||
stack.append(.{ .bin = expr.data.e_binary }) catch bun.outOfMemory();
|
||||
|
||||
// Build stack up of expressions
|
||||
var left: Expr = expr.data.e_binary.left;
|
||||
while (left.data.as(.e_binary)) |left_bin| {
|
||||
switch (left_bin.op) {
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> {
|
||||
stack.append(.{ .bin = left_bin }) catch bun.outOfMemory();
|
||||
left = left_bin.left;
|
||||
},
|
||||
else => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Ride the stack downwards
|
||||
var i = stack.items.len;
|
||||
var result = simplifyUnusedExpr(p, left) orelse Expr.empty;
|
||||
while (i > stack_bottom) {
|
||||
i -= 1;
|
||||
const top = stack.items[i];
|
||||
const visited_right = simplifyUnusedExpr(p, top.bin.right) orelse Expr.empty;
|
||||
result = result.joinWithComma(visited_right, p.allocator);
|
||||
}
|
||||
|
||||
return if (result.isMissing()) null else result;
|
||||
}
|
||||
|
||||
fn findIdentifiers(binding: Binding, decls: *std.ArrayList(G.Decl)) void {
|
||||
switch (binding.data) {
|
||||
.b_identifier => {
|
||||
decls.append(.{ .binding = binding }) catch unreachable;
|
||||
},
|
||||
.b_array => |array| {
|
||||
for (array.items) |item| {
|
||||
findIdentifiers(item.binding, decls);
|
||||
}
|
||||
},
|
||||
.b_object => |obj| {
|
||||
for (obj.properties) |item| {
|
||||
findIdentifiers(item.value, decls);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn shouldKeepStmtsInDeadControlFlow(stmts: []Stmt, allocator: Allocator) bool {
|
||||
for (stmts) |child| {
|
||||
if (shouldKeepStmtInDeadControlFlow(child, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// If this is in a dead branch, then we want to trim as much dead code as we
|
||||
/// can. Everything can be trimmed except for hoisted declarations ("var" and
|
||||
/// "function"), which affect the parent scope. For example:
|
||||
///
|
||||
/// function foo() {
|
||||
/// if (false) { var x; }
|
||||
/// x = 1;
|
||||
/// }
|
||||
///
|
||||
/// We can't trim the entire branch as dead or calling foo() will incorrectly
|
||||
/// assign to a global variable instead.
|
||||
///
|
||||
/// Caller is expected to first check `p.options.dead_code_elimination` so we only check it once.
|
||||
pub fn shouldKeepStmtInDeadControlFlow(stmt: Stmt, allocator: Allocator) bool {
|
||||
switch (stmt.data) {
|
||||
// Omit these statements entirely
|
||||
.s_empty, .s_expr, .s_throw, .s_return, .s_break, .s_continue, .s_class, .s_debugger => return false,
|
||||
|
||||
.s_local => |local| {
|
||||
if (local.kind != .k_var) {
|
||||
// Omit these statements entirely
|
||||
return false;
|
||||
}
|
||||
|
||||
// Omit everything except the identifiers
|
||||
|
||||
// common case: single var foo = blah, don't need to allocate
|
||||
if (local.decls.len == 1 and local.decls.ptr[0].binding.data == .b_identifier) {
|
||||
const prev = local.decls.ptr[0];
|
||||
stmt.data.s_local.decls.ptr[0] = G.Decl{ .binding = prev.binding };
|
||||
return true;
|
||||
}
|
||||
|
||||
var decls = std.ArrayList(G.Decl).initCapacity(allocator, local.decls.len) catch unreachable;
|
||||
for (local.decls.slice()) |decl| {
|
||||
findIdentifiers(decl.binding, &decls);
|
||||
}
|
||||
|
||||
local.decls.update(decls);
|
||||
return true;
|
||||
},
|
||||
|
||||
.s_block => |block| {
|
||||
return shouldKeepStmtsInDeadControlFlow(block.stmts, allocator);
|
||||
},
|
||||
|
||||
.s_try => |try_stmt| {
|
||||
if (shouldKeepStmtsInDeadControlFlow(try_stmt.body, allocator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (try_stmt.catch_) |*catch_stmt| {
|
||||
if (shouldKeepStmtsInDeadControlFlow(catch_stmt.body, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (try_stmt.finally) |*finally_stmt| {
|
||||
if (shouldKeepStmtsInDeadControlFlow(finally_stmt.stmts, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
.s_if => |_if_| {
|
||||
if (shouldKeepStmtInDeadControlFlow(_if_.yes, allocator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const no = _if_.no orelse return false;
|
||||
|
||||
return shouldKeepStmtInDeadControlFlow(no, allocator);
|
||||
},
|
||||
|
||||
.s_while => {
|
||||
return shouldKeepStmtInDeadControlFlow(stmt.data.s_while.body, allocator);
|
||||
},
|
||||
|
||||
.s_do_while => {
|
||||
return shouldKeepStmtInDeadControlFlow(stmt.data.s_do_while.body, allocator);
|
||||
},
|
||||
|
||||
.s_for => |__for__| {
|
||||
if (__for__.init) |init_| {
|
||||
if (shouldKeepStmtInDeadControlFlow(init_, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
|
||||
},
|
||||
|
||||
.s_for_in => |__for__| {
|
||||
return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
|
||||
},
|
||||
|
||||
.s_for_of => |__for__| {
|
||||
return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
|
||||
},
|
||||
|
||||
.s_label => |label| {
|
||||
return shouldKeepStmtInDeadControlFlow(label.stmt, allocator);
|
||||
},
|
||||
|
||||
else => return true,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if this expression is known to result in a primitive value (i.e.
|
||||
// null, undefined, boolean, number, bigint, or string), even if the expression
|
||||
// cannot be removed due to side effects.
|
||||
pub fn isPrimitiveWithSideEffects(data: Expr.Data) bool {
|
||||
switch (data) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_string,
|
||||
.e_inlined_enum,
|
||||
=> {
|
||||
return true;
|
||||
},
|
||||
.e_unary => |e| {
|
||||
switch (e.op) {
|
||||
// number or bigint
|
||||
.un_pos,
|
||||
.un_neg,
|
||||
.un_cpl,
|
||||
.un_pre_dec,
|
||||
.un_pre_inc,
|
||||
.un_post_dec,
|
||||
.un_post_inc,
|
||||
// boolean
|
||||
.un_not,
|
||||
.un_delete,
|
||||
// undefined
|
||||
.un_void,
|
||||
// string
|
||||
.un_typeof,
|
||||
=> {
|
||||
return true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_binary => |e| {
|
||||
switch (e.op) {
|
||||
// boolean
|
||||
.bin_lt,
|
||||
.bin_le,
|
||||
.bin_gt,
|
||||
.bin_ge,
|
||||
.bin_in,
|
||||
.bin_instanceof,
|
||||
.bin_loose_eq,
|
||||
.bin_loose_ne,
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
// string, number, or bigint
|
||||
.bin_add,
|
||||
.bin_add_assign,
|
||||
// number or bigint
|
||||
.bin_sub,
|
||||
.bin_mul,
|
||||
.bin_div,
|
||||
.bin_rem,
|
||||
.bin_pow,
|
||||
.bin_sub_assign,
|
||||
.bin_mul_assign,
|
||||
.bin_div_assign,
|
||||
.bin_rem_assign,
|
||||
.bin_pow_assign,
|
||||
.bin_shl,
|
||||
.bin_shr,
|
||||
.bin_u_shr,
|
||||
.bin_shl_assign,
|
||||
.bin_shr_assign,
|
||||
.bin_u_shr_assign,
|
||||
.bin_bitwise_or,
|
||||
.bin_bitwise_and,
|
||||
.bin_bitwise_xor,
|
||||
.bin_bitwise_or_assign,
|
||||
.bin_bitwise_and_assign,
|
||||
.bin_bitwise_xor_assign,
|
||||
=> {
|
||||
return true;
|
||||
},
|
||||
|
||||
// These always return one of the arguments unmodified
|
||||
.bin_logical_and,
|
||||
.bin_logical_or,
|
||||
.bin_nullish_coalescing,
|
||||
.bin_logical_and_assign,
|
||||
.bin_logical_or_assign,
|
||||
.bin_nullish_coalescing_assign,
|
||||
=> {
|
||||
return isPrimitiveWithSideEffects(e.left.data) and isPrimitiveWithSideEffects(e.right.data);
|
||||
},
|
||||
.bin_comma => {
|
||||
return isPrimitiveWithSideEffects(e.right.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_if => |e| {
|
||||
return isPrimitiveWithSideEffects(e.yes.data) and isPrimitiveWithSideEffects(e.no.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub const toTypeOf = Expr.Data.typeof;
|
||||
|
||||
pub fn toNullOrUndefined(p: anytype, exp: Expr.Data) Result {
|
||||
if (!p.options.features.dead_code_elimination) {
|
||||
// value should not be read if ok is false, all existing calls to this function already adhere to this
|
||||
return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
switch (exp) {
|
||||
// Never null or undefined
|
||||
.e_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => {
|
||||
return Result{ .value = false, .side_effects = .no_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
.e_object, .e_array, .e_class => {
|
||||
return Result{ .value = false, .side_effects = .could_have_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
// always a null or undefined
|
||||
.e_null, .e_undefined => {
|
||||
return Result{ .value = true, .side_effects = .no_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
.e_unary => |e| {
|
||||
switch (e.op) {
|
||||
// Always number or bigint
|
||||
.un_pos,
|
||||
.un_neg,
|
||||
.un_cpl,
|
||||
.un_pre_dec,
|
||||
.un_pre_inc,
|
||||
.un_post_dec,
|
||||
.un_post_inc,
|
||||
|
||||
// Always boolean
|
||||
.un_not,
|
||||
.un_typeof,
|
||||
.un_delete,
|
||||
=> {
|
||||
return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
},
|
||||
|
||||
// Always undefined
|
||||
.un_void => {
|
||||
return Result{ .value = true, .side_effects = .could_have_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
.e_binary => |e| {
|
||||
switch (e.op) {
|
||||
// always string or number or bigint
|
||||
.bin_add,
|
||||
.bin_add_assign,
|
||||
// always number or bigint
|
||||
.bin_sub,
|
||||
.bin_mul,
|
||||
.bin_div,
|
||||
.bin_rem,
|
||||
.bin_pow,
|
||||
.bin_sub_assign,
|
||||
.bin_mul_assign,
|
||||
.bin_div_assign,
|
||||
.bin_rem_assign,
|
||||
.bin_pow_assign,
|
||||
.bin_shl,
|
||||
.bin_shr,
|
||||
.bin_u_shr,
|
||||
.bin_shl_assign,
|
||||
.bin_shr_assign,
|
||||
.bin_u_shr_assign,
|
||||
.bin_bitwise_or,
|
||||
.bin_bitwise_and,
|
||||
.bin_bitwise_xor,
|
||||
.bin_bitwise_or_assign,
|
||||
.bin_bitwise_and_assign,
|
||||
.bin_bitwise_xor_assign,
|
||||
// always boolean
|
||||
.bin_lt,
|
||||
.bin_le,
|
||||
.bin_gt,
|
||||
.bin_ge,
|
||||
.bin_in,
|
||||
.bin_instanceof,
|
||||
.bin_loose_eq,
|
||||
.bin_loose_ne,
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
=> {
|
||||
return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
},
|
||||
|
||||
.bin_comma => {
|
||||
const res = toNullOrUndefined(p, e.right.data);
|
||||
if (res.ok) {
|
||||
return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_inlined_enum => |inlined| {
|
||||
return toNullOrUndefined(p, inlined.value.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
}
|
||||
|
||||
pub fn toBoolean(p: anytype, exp: Expr.Data) Result {
|
||||
// Only do this check once.
|
||||
if (!p.options.features.dead_code_elimination) {
|
||||
// value should not be read if ok is false, all existing calls to this function already adhere to this
|
||||
return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
|
||||
return toBooleanWithoutDCECheck(exp);
|
||||
}
|
||||
|
||||
// Avoid passing through *P
|
||||
// This is a very recursive function.
|
||||
fn toBooleanWithoutDCECheck(exp: Expr.Data) Result {
|
||||
switch (exp) {
|
||||
.e_null, .e_undefined => {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_boolean => |e| {
|
||||
return Result{ .ok = true, .value = e.value, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_number => |e| {
|
||||
return Result{ .ok = true, .value = e.value != 0.0 and !std.math.isNan(e.value), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_big_int => |e| {
|
||||
return Result{ .ok = true, .value = !strings.eqlComptime(e.value, "0"), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_string => |e| {
|
||||
return Result{ .ok = true, .value = e.isPresent(), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_function, .e_arrow, .e_reg_exp => {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_object, .e_array, .e_class => {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.e_unary => |e_| {
|
||||
switch (e_.op) {
|
||||
.un_void => {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.un_typeof => {
|
||||
// Never an empty string
|
||||
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.un_not => {
|
||||
const result = toBooleanWithoutDCECheck(e_.value.data);
|
||||
if (result.ok) {
|
||||
return .{ .ok = true, .value = !result.value, .side_effects = result.side_effects };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_binary => |e_| {
|
||||
switch (e_.op) {
|
||||
.bin_logical_or => {
|
||||
// "anything || truthy" is truthy
|
||||
const result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
if (result.value and result.ok) {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
},
|
||||
.bin_logical_and => {
|
||||
// "anything && falsy" is falsy
|
||||
const result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
if (!result.value and result.ok) {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
},
|
||||
.bin_comma => {
|
||||
// "anything, truthy/falsy" is truthy/falsy
|
||||
var result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
if (result.ok) {
|
||||
result.side_effects = .could_have_side_effects;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.bin_gt => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num > right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_lt => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num < right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_le => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num <= right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_ge => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num >= right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_inlined_enum => |inlined| {
|
||||
return toBooleanWithoutDCECheck(inlined.value.data);
|
||||
},
|
||||
.e_special => |special| switch (special) {
|
||||
.module_exports,
|
||||
.resolved_specifier_string,
|
||||
.hot_data,
|
||||
=> {},
|
||||
.hot_accept,
|
||||
.hot_accept_visited,
|
||||
.hot_enabled,
|
||||
=> return .{ .ok = true, .value = true, .side_effects = .no_side_effects },
|
||||
.hot_disabled,
|
||||
=> return .{ .ok = true, .value = false, .side_effects = .no_side_effects },
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
}
|
||||
};
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const options = @import("../options.zig");
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const assert = bun.assert;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const Binding = js_ast.Binding;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const Stmt = js_ast.Stmt;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Decl = G.Decl;
|
||||
const Property = G.Property;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -1,472 +0,0 @@
|
||||
// This function is taken from the official TypeScript compiler source code:
|
||||
// https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts
|
||||
pub fn canFollowTypeArgumentsInExpression(p: anytype) bool {
|
||||
return switch (p.lexer.token) {
|
||||
// These are the only tokens can legally follow a type argument list. So we
|
||||
// definitely want to treat them as type arg lists.
|
||||
.t_open_paren, // foo<x>(
|
||||
.t_no_substitution_template_literal, // foo<T> `...`
|
||||
// foo<T> `...${100}...`
|
||||
.t_template_head,
|
||||
=> true,
|
||||
|
||||
// A type argument list followed by `<` never makes sense, and a type argument list followed
|
||||
// by `>` is ambiguous with a (re-scanned) `>>` operator, so we disqualify both. Also, in
|
||||
// this context, `+` and `-` are unary operators, not binary operators.
|
||||
.t_less_than,
|
||||
.t_greater_than,
|
||||
.t_plus,
|
||||
.t_minus,
|
||||
// TypeScript always sees "t_greater_than" instead of these tokens since
|
||||
// their scanner works a little differently than our lexer. So since
|
||||
// "t_greater_than" is forbidden above, we also forbid these too.
|
||||
.t_greater_than_equals,
|
||||
.t_greater_than_greater_than,
|
||||
.t_greater_than_greater_than_equals,
|
||||
.t_greater_than_greater_than_greater_than,
|
||||
.t_greater_than_greater_than_greater_than_equals,
|
||||
=> false,
|
||||
|
||||
// We favor the type argument list interpretation when it is immediately followed by
|
||||
// a line break, a binary operator, or something that can't start an expression.
|
||||
else => p.lexer.has_newline_before or isBinaryOperator(p) or !isStartOfExpression(p),
|
||||
};
|
||||
}
|
||||
|
||||
pub const Metadata = union(enum) {
|
||||
m_none: void,
|
||||
|
||||
m_never: void,
|
||||
m_unknown: void,
|
||||
m_any: void,
|
||||
m_void: void,
|
||||
m_null: void,
|
||||
m_undefined: void,
|
||||
m_function: void,
|
||||
m_array: void,
|
||||
m_boolean: void,
|
||||
m_string: void,
|
||||
m_object: void,
|
||||
m_number: void,
|
||||
m_bigint: void,
|
||||
m_symbol: void,
|
||||
m_promise: void,
|
||||
m_identifier: Ref,
|
||||
m_dot: List(Ref),
|
||||
|
||||
pub const default: @This() = .m_none;
|
||||
|
||||
// the logic in finishUnion, mergeUnion, finishIntersection and mergeIntersection is
|
||||
// translated from:
|
||||
// https://github.com/microsoft/TypeScript/blob/e0a324b0503be479f2b33fd2e17c6e86c94d1297/src/compiler/transformers/typeSerializer.ts#L402
|
||||
|
||||
/// Return the final union type if possible, or return null to continue merging.
|
||||
///
|
||||
/// If the current type is m_never, m_null, or m_undefined assign the current type
|
||||
/// to m_none and return null to ensure it's always replaced by the next type.
|
||||
pub fn finishUnion(current: *@This(), p: anytype) ?@This() {
|
||||
return switch (current.*) {
|
||||
.m_identifier => |ref| {
|
||||
if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) {
|
||||
return .m_object;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
.m_unknown,
|
||||
.m_any,
|
||||
.m_object,
|
||||
=> .m_object,
|
||||
|
||||
.m_never,
|
||||
.m_null,
|
||||
.m_undefined,
|
||||
=> {
|
||||
current.* = .m_none;
|
||||
return null;
|
||||
},
|
||||
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn mergeUnion(result: *@This(), left: @This()) void {
|
||||
if (left != .m_none) {
|
||||
if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) {
|
||||
result.* = switch (result.*) {
|
||||
.m_never,
|
||||
.m_undefined,
|
||||
.m_null,
|
||||
=> left,
|
||||
|
||||
else => .m_object,
|
||||
};
|
||||
} else {
|
||||
switch (result.*) {
|
||||
.m_identifier => |ref| {
|
||||
if (!ref.eql(left.m_identifier)) {
|
||||
result.* = .m_object;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// always take the next value if left is m_none
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the final intersection type if possible, or return null to continue merging.
|
||||
///
|
||||
/// If the current type is m_unknown, m_null, or m_undefined assign the current type
|
||||
/// to m_none and return null to ensure it's always replaced by the next type.
|
||||
pub fn finishIntersection(current: *@This(), p: anytype) ?@This() {
|
||||
return switch (current.*) {
|
||||
.m_identifier => |ref| {
|
||||
if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) {
|
||||
return .m_object;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// ensure m_never is the final type
|
||||
.m_never => .m_never,
|
||||
|
||||
.m_any,
|
||||
.m_object,
|
||||
=> .m_object,
|
||||
|
||||
.m_unknown,
|
||||
.m_null,
|
||||
.m_undefined,
|
||||
=> {
|
||||
current.* = .m_none;
|
||||
return null;
|
||||
},
|
||||
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn mergeIntersection(result: *@This(), left: @This()) void {
|
||||
if (left != .m_none) {
|
||||
if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) {
|
||||
result.* = switch (result.*) {
|
||||
.m_unknown,
|
||||
.m_undefined,
|
||||
.m_null,
|
||||
=> left,
|
||||
|
||||
// ensure m_never is the final type
|
||||
.m_never => .m_never,
|
||||
|
||||
else => .m_object,
|
||||
};
|
||||
} else {
|
||||
switch (result.*) {
|
||||
.m_identifier => |ref| {
|
||||
if (!ref.eql(left.m_identifier)) {
|
||||
result.* = .m_object;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// make sure intersection of only m_unknown serializes to "undefined"
|
||||
// instead of "Object"
|
||||
if (result.* == .m_unknown) {
|
||||
result.* = .m_undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn isTSArrowFnJSX(p: anytype) !bool {
|
||||
const old_lexer = p.lexer;
|
||||
|
||||
try p.lexer.next();
|
||||
// Look ahead to see if this should be an arrow function instead
|
||||
var is_ts_arrow_fn = false;
|
||||
|
||||
if (p.lexer.token == .t_const) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
if (p.lexer.token == .t_identifier) {
|
||||
try p.lexer.next();
|
||||
if (p.lexer.token == .t_comma) {
|
||||
is_ts_arrow_fn = true;
|
||||
} else if (p.lexer.token == .t_extends) {
|
||||
try p.lexer.next();
|
||||
is_ts_arrow_fn = p.lexer.token != .t_equals and p.lexer.token != .t_greater_than;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the lexer
|
||||
p.lexer.restore(&old_lexer);
|
||||
return is_ts_arrow_fn;
|
||||
}
|
||||
|
||||
// This function is taken from the official TypeScript compiler source code:
|
||||
// https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts
|
||||
fn isBinaryOperator(p: anytype) bool {
|
||||
return switch (p.lexer.token) {
|
||||
.t_in => p.allow_in,
|
||||
|
||||
.t_question_question,
|
||||
.t_bar_bar,
|
||||
.t_ampersand_ampersand,
|
||||
.t_bar,
|
||||
.t_caret,
|
||||
.t_ampersand,
|
||||
.t_equals_equals,
|
||||
.t_exclamation_equals,
|
||||
.t_equals_equals_equals,
|
||||
.t_exclamation_equals_equals,
|
||||
.t_less_than,
|
||||
.t_greater_than,
|
||||
.t_less_than_equals,
|
||||
.t_greater_than_equals,
|
||||
.t_instanceof,
|
||||
.t_less_than_less_than,
|
||||
.t_greater_than_greater_than,
|
||||
.t_greater_than_greater_than_greater_than,
|
||||
.t_plus,
|
||||
.t_minus,
|
||||
.t_asterisk,
|
||||
.t_slash,
|
||||
.t_percent,
|
||||
.t_asterisk_asterisk,
|
||||
=> true,
|
||||
.t_identifier => p.lexer.isContextualKeyword("as") or p.lexer.isContextualKeyword("satisfies"),
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
// This function is taken from the official TypeScript compiler source code:
|
||||
// https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts
|
||||
fn isStartOfLeftHandSideExpression(p: anytype) bool {
|
||||
return switch (p.lexer.token) {
|
||||
.t_this,
|
||||
.t_super,
|
||||
.t_null,
|
||||
.t_true,
|
||||
.t_false,
|
||||
.t_numeric_literal,
|
||||
.t_big_integer_literal,
|
||||
.t_string_literal,
|
||||
.t_no_substitution_template_literal,
|
||||
.t_template_head,
|
||||
.t_open_paren,
|
||||
.t_open_bracket,
|
||||
.t_open_brace,
|
||||
.t_function,
|
||||
.t_class,
|
||||
.t_new,
|
||||
.t_slash,
|
||||
.t_slash_equals,
|
||||
.t_identifier,
|
||||
=> true,
|
||||
.t_import => lookAheadNextTokenIsOpenParenOrLessThanOrDot(p),
|
||||
else => isIdentifier(p),
|
||||
};
|
||||
}
|
||||
|
||||
fn lookAheadNextTokenIsOpenParenOrLessThanOrDot(p: anytype) bool {
|
||||
const old_lexer = p.lexer;
|
||||
const old_log_disabled = p.lexer.is_log_disabled;
|
||||
p.lexer.is_log_disabled = true;
|
||||
defer {
|
||||
p.lexer.restore(&old_lexer);
|
||||
p.lexer.is_log_disabled = old_log_disabled;
|
||||
}
|
||||
p.lexer.next() catch {};
|
||||
|
||||
return switch (p.lexer.token) {
|
||||
.t_open_paren, .t_less_than, .t_dot => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
// This function is taken from the official TypeScript compiler source code:
|
||||
// https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts
|
||||
fn isIdentifier(p: anytype) bool {
|
||||
if (p.lexer.token == .t_identifier) {
|
||||
// If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is
|
||||
// considered a keyword and is not an identifier.
|
||||
if (p.fn_or_arrow_data_parse.allow_yield != .allow_ident and strings.eqlComptime(p.lexer.identifier, "yield")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have an 'await' keyword, and we're in the [await] context, then 'await' is
|
||||
// considered a keyword and is not an identifier.
|
||||
if (p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(p.lexer.identifier, "await")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn isStartOfExpression(p: anytype) bool {
|
||||
if (isStartOfLeftHandSideExpression(p))
|
||||
return true;
|
||||
|
||||
switch (p.lexer.token) {
|
||||
.t_plus,
|
||||
.t_minus,
|
||||
.t_tilde,
|
||||
.t_exclamation,
|
||||
.t_delete,
|
||||
.t_typeof,
|
||||
.t_void,
|
||||
.t_plus_plus,
|
||||
.t_minus_minus,
|
||||
.t_less_than,
|
||||
.t_private_identifier,
|
||||
.t_at,
|
||||
=> return true,
|
||||
else => {
|
||||
if (p.lexer.token == .t_identifier and (strings.eqlComptime(p.lexer.identifier, "await") or strings.eqlComptime(p.lexer.identifier, "yield"))) {
|
||||
// Yield/await always starts an expression. Either it is an identifier (in which case
|
||||
// it is definitely an expression). Or it's a keyword (either because we're in
|
||||
// a generator or async function, or in strict mode (or both)) and it started a yield or await expression.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Error tolerance. If we see the start of some binary operator, we consider
|
||||
// that the start of an expression. That way we'll parse out a missing identifier,
|
||||
// give a good message about an identifier being missing, and then consume the
|
||||
// rest of the binary expression.
|
||||
if (isBinaryOperator(p)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isIdentifier(p);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub const Identifier = struct {
|
||||
pub const StmtIdentifier = enum {
|
||||
s_type,
|
||||
|
||||
s_namespace,
|
||||
|
||||
s_abstract,
|
||||
|
||||
s_module,
|
||||
|
||||
s_interface,
|
||||
|
||||
s_declare,
|
||||
};
|
||||
pub fn forStr(str: string) ?StmtIdentifier {
|
||||
switch (str.len) {
|
||||
"type".len => return if (strings.eqlComptimeIgnoreLen(str, "type"))
|
||||
.s_type
|
||||
else
|
||||
null,
|
||||
"interface".len => {
|
||||
if (strings.eqlComptime(str, "interface")) {
|
||||
return .s_interface;
|
||||
} else if (strings.eqlComptime(str, "namespace")) {
|
||||
return .s_namespace;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
"abstract".len => {
|
||||
if (strings.eqlComptime(str, "abstract")) {
|
||||
return .s_abstract;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
"declare".len => {
|
||||
if (strings.eqlComptime(str, "declare")) {
|
||||
return .s_declare;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
"module".len => {
|
||||
if (strings.eqlComptime(str, "module")) {
|
||||
return .s_module;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
pub const IMap = bun.ComptimeStringMap(Kind, .{
|
||||
.{ "unique", .unique },
|
||||
.{ "abstract", .abstract },
|
||||
.{ "asserts", .asserts },
|
||||
|
||||
.{ "keyof", .prefix_keyof },
|
||||
.{ "readonly", .prefix_readonly },
|
||||
|
||||
.{ "any", .primitive_any },
|
||||
.{ "never", .primitive_never },
|
||||
.{ "unknown", .primitive_unknown },
|
||||
.{ "undefined", .primitive_undefined },
|
||||
.{ "object", .primitive_object },
|
||||
.{ "number", .primitive_number },
|
||||
.{ "string", .primitive_string },
|
||||
.{ "boolean", .primitive_boolean },
|
||||
.{ "bigint", .primitive_bigint },
|
||||
.{ "symbol", .primitive_symbol },
|
||||
|
||||
.{ "infer", .infer },
|
||||
});
|
||||
pub const Kind = enum {
|
||||
normal,
|
||||
unique,
|
||||
abstract,
|
||||
asserts,
|
||||
prefix_keyof,
|
||||
prefix_readonly,
|
||||
primitive_any,
|
||||
primitive_never,
|
||||
primitive_unknown,
|
||||
primitive_undefined,
|
||||
primitive_object,
|
||||
primitive_number,
|
||||
primitive_string,
|
||||
primitive_boolean,
|
||||
primitive_bigint,
|
||||
primitive_symbol,
|
||||
infer,
|
||||
};
|
||||
};
|
||||
|
||||
pub const SkipTypeOptions = enum {
|
||||
is_return_type,
|
||||
is_index_signature,
|
||||
allow_tuple_labels,
|
||||
disallow_conditional_types,
|
||||
|
||||
pub const Bitset = std.enums.EnumSet(@This());
|
||||
pub const empty = Bitset.initEmpty();
|
||||
};
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_lexer = bun.js_lexer;
|
||||
const T = js_lexer.T;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const Ref = js_parser.Ref;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
@@ -1,233 +0,0 @@
|
||||
/// Concatenate two `E.String`s, mutating BOTH inputs
|
||||
/// unless `has_inlined_enum_poison` is set.
|
||||
///
|
||||
/// Currently inlined enum poison refers to where mutation would cause output
|
||||
/// bugs due to inlined enum values sharing `E.String`s. If a new use case
|
||||
/// besides inlined enums comes up to set this to true, please rename the
|
||||
/// variable and document it.
|
||||
fn joinStrings(left: *const E.String, right: *const E.String, has_inlined_enum_poison: bool) E.String {
|
||||
var new = if (has_inlined_enum_poison)
|
||||
// Inlined enums can be shared by multiple call sites. In
|
||||
// this case, we need to ensure that the ENTIRE rope is
|
||||
// cloned. In other situations, the lhs doesn't have any
|
||||
// other owner, so it is fine to mutate `lhs.data.end.next`.
|
||||
//
|
||||
// Consider the following case:
|
||||
// const enum A {
|
||||
// B = "a" + "b",
|
||||
// D = B + "d",
|
||||
// };
|
||||
// console.log(A.B, A.D);
|
||||
left.cloneRopeNodes()
|
||||
else
|
||||
left.*;
|
||||
|
||||
// Similarly, the right side has to be cloned for an enum rope too.
|
||||
//
|
||||
// Consider the following case:
|
||||
// const enum A {
|
||||
// B = "1" + "2",
|
||||
// C = ("3" + B) + "4",
|
||||
// };
|
||||
// console.log(A.B, A.C);
|
||||
const rhs_clone = Expr.Data.Store.append(E.String, if (has_inlined_enum_poison)
|
||||
right.cloneRopeNodes()
|
||||
else
|
||||
right.*);
|
||||
|
||||
new.push(rhs_clone);
|
||||
new.prefer_template = new.prefer_template or rhs_clone.prefer_template;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
/// Transforming the left operand into a string is not safe if it comes from a
|
||||
/// nested AST node.
|
||||
const FoldStringAdditionKind = enum {
|
||||
// "x" + "y" -> "xy"
|
||||
// 1 + "y" -> "1y"
|
||||
normal,
|
||||
// a + "x" + "y" -> a + "xy"
|
||||
// a + 1 + "y" -> a + 1 + y
|
||||
nested_left,
|
||||
};
|
||||
|
||||
/// NOTE: unlike esbuild's js_ast_helpers.FoldStringAddition, this does mutate
|
||||
/// the input AST in the case of rope strings
|
||||
pub fn foldStringAddition(l: Expr, r: Expr, allocator: std.mem.Allocator, kind: FoldStringAdditionKind) ?Expr {
|
||||
// "See through" inline enum constants
|
||||
// TODO: implement foldAdditionPreProcess to fold some more things :)
|
||||
var lhs = l.unwrapInlined();
|
||||
var rhs = r.unwrapInlined();
|
||||
|
||||
if (kind != .nested_left) {
|
||||
// See comment on `FoldStringAdditionKind` for examples
|
||||
switch (rhs.data) {
|
||||
.e_string, .e_template => {
|
||||
if (lhs.toStringExprWithoutSideEffects(allocator)) |str| {
|
||||
lhs = str;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
switch (lhs.data) {
|
||||
.e_string => |left| {
|
||||
if (rhs.toStringExprWithoutSideEffects(allocator)) |str| {
|
||||
rhs = str;
|
||||
}
|
||||
|
||||
if (left.isUTF8()) {
|
||||
switch (rhs.data) {
|
||||
// "bar" + "baz" => "barbaz"
|
||||
.e_string => |right| {
|
||||
if (right.isUTF8()) {
|
||||
const has_inlined_enum_poison =
|
||||
l.data == .e_inlined_enum or
|
||||
r.data == .e_inlined_enum;
|
||||
|
||||
return Expr.init(E.String, joinStrings(
|
||||
left,
|
||||
right,
|
||||
has_inlined_enum_poison,
|
||||
), lhs.loc);
|
||||
}
|
||||
},
|
||||
// "bar" + `baz${bar}` => `barbaz${bar}`
|
||||
.e_template => |right| {
|
||||
if (right.head.isUTF8()) {
|
||||
return Expr.init(E.Template, E.Template{
|
||||
.parts = right.parts,
|
||||
.head = .{ .cooked = joinStrings(
|
||||
left,
|
||||
&right.head.cooked,
|
||||
l.data == .e_inlined_enum,
|
||||
) },
|
||||
}, l.loc);
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// other constant-foldable ast nodes would have been converted to .e_string
|
||||
},
|
||||
}
|
||||
|
||||
// "'x' + `y${z}`" => "`xy${z}`"
|
||||
if (rhs.data == .e_template and rhs.data.e_template.tag == null) {}
|
||||
}
|
||||
|
||||
if (left.len() == 0 and rhs.knownPrimitive() == .string) {
|
||||
return rhs;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
.e_template => |left| {
|
||||
// "`${x}` + 0" => "`${x}` + '0'"
|
||||
if (rhs.toStringExprWithoutSideEffects(allocator)) |str| {
|
||||
rhs = str;
|
||||
}
|
||||
|
||||
if (left.tag == null) {
|
||||
switch (rhs.data) {
|
||||
// `foo${bar}` + "baz" => `foo${bar}baz`
|
||||
.e_string => |right| {
|
||||
if (right.isUTF8()) {
|
||||
// Mutation of this node is fine because it will be not
|
||||
// be shared by other places. Note that e_template will
|
||||
// be treated by enums as strings, but will not be
|
||||
// inlined unless they could be converted into
|
||||
// .e_string.
|
||||
if (left.parts.len > 0) {
|
||||
const i = left.parts.len - 1;
|
||||
const last = left.parts[i];
|
||||
if (last.tail.isUTF8()) {
|
||||
left.parts[i].tail = .{ .cooked = joinStrings(
|
||||
&last.tail.cooked,
|
||||
right,
|
||||
r.data == .e_inlined_enum,
|
||||
) };
|
||||
return lhs;
|
||||
}
|
||||
} else {
|
||||
if (left.head.isUTF8()) {
|
||||
left.head = .{ .cooked = joinStrings(
|
||||
&left.head.cooked,
|
||||
right,
|
||||
r.data == .e_inlined_enum,
|
||||
) };
|
||||
return lhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// `foo${bar}` + `a${hi}b` => `foo${bar}a${hi}b`
|
||||
.e_template => |right| {
|
||||
if (right.tag == null and right.head.isUTF8()) {
|
||||
if (left.parts.len > 0) {
|
||||
const i = left.parts.len - 1;
|
||||
const last = left.parts[i];
|
||||
if (last.tail.isUTF8() and right.head.isUTF8()) {
|
||||
left.parts[i].tail = .{ .cooked = joinStrings(
|
||||
&last.tail.cooked,
|
||||
&right.head.cooked,
|
||||
r.data == .e_inlined_enum,
|
||||
) };
|
||||
|
||||
left.parts = if (right.parts.len == 0)
|
||||
left.parts
|
||||
else
|
||||
std.mem.concat(
|
||||
allocator,
|
||||
E.TemplatePart,
|
||||
&.{ left.parts, right.parts },
|
||||
) catch bun.outOfMemory();
|
||||
return lhs;
|
||||
}
|
||||
} else {
|
||||
if (left.head.isUTF8() and right.head.isUTF8()) {
|
||||
left.head = .{ .cooked = joinStrings(
|
||||
&left.head.cooked,
|
||||
&right.head.cooked,
|
||||
r.data == .e_inlined_enum,
|
||||
) };
|
||||
left.parts = right.parts;
|
||||
return lhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// other constant-foldable ast nodes would have been converted to .e_string
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
else => {
|
||||
// other constant-foldable ast nodes would have been converted to .e_string
|
||||
},
|
||||
}
|
||||
|
||||
if (rhs.data.as(.e_string)) |right| {
|
||||
if (right.len() == 0 and lhs.knownPrimitive() == .string) {
|
||||
return lhs;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const bun = @import("bun");
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const B = js_ast.B;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
@@ -1,725 +0,0 @@
|
||||
pub fn AstMaybe(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
|
||||
pub fn maybeRelocateVarsToTopLevel(p: *P, decls: []const G.Decl, mode: RelocateVars.Mode) RelocateVars {
|
||||
// Only do this when the scope is not already top-level and when we're not inside a function.
|
||||
if (p.current_scope == p.module_scope) {
|
||||
return .{ .ok = false };
|
||||
}
|
||||
|
||||
var scope = p.current_scope;
|
||||
while (!scope.kindStopsHoisting()) {
|
||||
if (comptime Environment.allow_assert) assert(scope.parent != null);
|
||||
scope = scope.parent.?;
|
||||
}
|
||||
|
||||
if (scope != p.module_scope) {
|
||||
return .{ .ok = false };
|
||||
}
|
||||
|
||||
var value: Expr = Expr{ .loc = logger.Loc.Empty, .data = Expr.Data{ .e_missing = E.Missing{} } };
|
||||
|
||||
for (decls) |decl| {
|
||||
const binding = Binding.toExpr(
|
||||
&decl.binding,
|
||||
p.to_expr_wrapper_hoisted,
|
||||
);
|
||||
if (decl.value) |decl_value| {
|
||||
value = value.joinWithComma(Expr.assign(binding, decl_value), p.allocator);
|
||||
} else if (mode == .for_in_or_for_of) {
|
||||
value = value.joinWithComma(binding, p.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
if (value.data == .e_missing) {
|
||||
return .{ .ok = true };
|
||||
}
|
||||
|
||||
return .{ .stmt = p.s(S.SExpr{ .value = value }, value.loc), .ok = true };
|
||||
}
|
||||
|
||||
// EDot nodes represent a property access. This function may return an
|
||||
// expression to replace the property access with. It assumes that the
|
||||
// target of the EDot expression has already been visited.
|
||||
pub fn maybeRewritePropertyAccess(
|
||||
p: *P,
|
||||
loc: logger.Loc,
|
||||
target: js_ast.Expr,
|
||||
name: string,
|
||||
name_loc: logger.Loc,
|
||||
identifier_opts: IdentifierOpts,
|
||||
) ?Expr {
|
||||
sw: switch (target.data) {
|
||||
.e_identifier => |id| {
|
||||
// Rewrite property accesses on explicit namespace imports as an identifier.
|
||||
// This lets us replace them easily in the printer to rebind them to
|
||||
// something else without paying the cost of a whole-tree traversal during
|
||||
// module linking just to rewrite these EDot expressions.
|
||||
if (p.options.bundle) {
|
||||
if (p.import_items_for_namespace.getPtr(id.ref)) |import_items| {
|
||||
const ref = (import_items.get(name) orelse brk: {
|
||||
// Generate a new import item symbol in the module scope
|
||||
const new_item = LocRef{
|
||||
.loc = name_loc,
|
||||
.ref = p.newSymbol(.import, name) catch unreachable,
|
||||
};
|
||||
p.module_scope.generated.push(p.allocator, new_item.ref.?) catch unreachable;
|
||||
|
||||
import_items.put(name, new_item) catch unreachable;
|
||||
p.is_import_item.put(p.allocator, new_item.ref.?, {}) catch unreachable;
|
||||
|
||||
var symbol = &p.symbols.items[new_item.ref.?.innerIndex()];
|
||||
|
||||
// Mark this as generated in case it's missing. We don't want to
|
||||
// generate errors for missing import items that are automatically
|
||||
// generated.
|
||||
symbol.import_item_status = .generated;
|
||||
|
||||
break :brk new_item;
|
||||
}).ref.?;
|
||||
|
||||
// Undo the usage count for the namespace itself. This is used later
|
||||
// to detect whether the namespace symbol has ever been "captured"
|
||||
// or whether it has just been used to read properties off of.
|
||||
//
|
||||
// The benefit of doing this is that if both this module and the
|
||||
// imported module end up in the same module group and the namespace
|
||||
// symbol has never been captured, then we don't need to generate
|
||||
// any code for the namespace at all.
|
||||
p.ignoreUsage(id.ref);
|
||||
|
||||
// Track how many times we've referenced this symbol
|
||||
p.recordUsage(ref);
|
||||
|
||||
return p.handleIdentifier(
|
||||
name_loc,
|
||||
E.Identifier{ .ref = ref },
|
||||
name,
|
||||
.{
|
||||
.assign_target = identifier_opts.assign_target,
|
||||
.is_call_target = identifier_opts.is_call_target,
|
||||
.is_delete_target = identifier_opts.is_delete_target,
|
||||
|
||||
// If this expression is used as the target of a call expression, make
|
||||
// sure the value of "this" is preserved.
|
||||
.was_originally_identifier = false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!p.is_control_flow_dead and id.ref.eql(p.module_ref)) {
|
||||
// Rewrite "module.require()" to "require()" for Webpack compatibility.
|
||||
// See https://github.com/webpack/webpack/pull/7750 for more info.
|
||||
// This also makes correctness a little easier.
|
||||
if (identifier_opts.is_call_target and strings.eqlComptime(name, "require")) {
|
||||
p.ignoreUsage(p.module_ref);
|
||||
return p.valueForRequire(name_loc);
|
||||
} else if (!p.commonjs_named_exports_deoptimized and strings.eqlComptime(name, "exports")) {
|
||||
if (identifier_opts.assign_target != .none) {
|
||||
p.commonjs_module_exports_assigned_deoptimized = true;
|
||||
}
|
||||
|
||||
// Detect if we are doing
|
||||
//
|
||||
// module.exports = {
|
||||
// foo: "bar"
|
||||
// }
|
||||
//
|
||||
// Note that it cannot be any of these:
|
||||
//
|
||||
// module.exports += { };
|
||||
// delete module.exports = {};
|
||||
// module.exports()
|
||||
if (!(identifier_opts.is_call_target or identifier_opts.is_delete_target) and
|
||||
identifier_opts.assign_target == .replace and
|
||||
p.stmt_expr_value == .e_binary and
|
||||
p.stmt_expr_value.e_binary.op == .bin_assign)
|
||||
{
|
||||
if (
|
||||
// if it's not top-level, don't do this
|
||||
p.module_scope != p.current_scope or
|
||||
// if you do
|
||||
//
|
||||
// exports.foo = 123;
|
||||
// module.exports = {};
|
||||
//
|
||||
// that's a de-opt.
|
||||
p.commonjs_named_exports.count() > 0 or
|
||||
|
||||
// anything which is not module.exports = {} is a de-opt.
|
||||
p.stmt_expr_value.e_binary.right.data != .e_object or
|
||||
p.stmt_expr_value.e_binary.left.data != .e_dot or
|
||||
!strings.eqlComptime(p.stmt_expr_value.e_binary.left.data.e_dot.name, "exports") or
|
||||
p.stmt_expr_value.e_binary.left.data.e_dot.target.data != .e_identifier or
|
||||
!p.stmt_expr_value.e_binary.left.data.e_dot.target.data.e_identifier.ref.eql(p.module_ref))
|
||||
{
|
||||
p.deoptimizeCommonJSNamedExports();
|
||||
return null;
|
||||
}
|
||||
|
||||
const props: []const G.Property = p.stmt_expr_value.e_binary.right.data.e_object.properties.slice();
|
||||
for (props) |prop| {
|
||||
// if it's not a trivial object literal, de-opt
|
||||
if (prop.kind != .normal or
|
||||
prop.key == null or
|
||||
prop.key.?.data != .e_string or
|
||||
prop.flags.contains(Flags.Property.is_method) or
|
||||
prop.flags.contains(Flags.Property.is_computed) or
|
||||
prop.flags.contains(Flags.Property.is_spread) or
|
||||
prop.flags.contains(Flags.Property.is_static) or
|
||||
// If it creates a new scope, we can't do this optimization right now
|
||||
// Our scope order verification stuff will get mad
|
||||
// But we should let you do module.exports = { bar: foo(), baz: 123 }
|
||||
// just not module.exports = { bar: function() {} }
|
||||
// just not module.exports = { bar() {} }
|
||||
switch (prop.value.?.data) {
|
||||
.e_commonjs_export_identifier, .e_import_identifier, .e_identifier => false,
|
||||
.e_call => |call| switch (call.target.data) {
|
||||
.e_commonjs_export_identifier, .e_import_identifier, .e_identifier => false,
|
||||
else => |call_target| !@as(Expr.Tag, call_target).isPrimitiveLiteral(),
|
||||
},
|
||||
else => !prop.value.?.isPrimitiveLiteral(),
|
||||
})
|
||||
{
|
||||
p.deoptimizeCommonJSNamedExports();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// empty object de-opts because otherwise the statement becomes
|
||||
// <empty space> = {};
|
||||
p.deoptimizeCommonJSNamedExports();
|
||||
return null;
|
||||
}
|
||||
|
||||
var stmts = std.ArrayList(Stmt).initCapacity(p.allocator, props.len * 2) catch unreachable;
|
||||
var decls = p.allocator.alloc(Decl, props.len) catch unreachable;
|
||||
var clause_items = p.allocator.alloc(js_ast.ClauseItem, props.len) catch unreachable;
|
||||
|
||||
for (props) |prop| {
|
||||
const key = prop.key.?.data.e_string.string(p.allocator) catch unreachable;
|
||||
const visited_value = p.visitExpr(prop.value.?);
|
||||
const value = SideEffects.simplifyUnusedExpr(p, visited_value) orelse visited_value;
|
||||
|
||||
// We are doing `module.exports = { ... }`
|
||||
// lets rewrite it to a series of what will become export assignments
|
||||
const named_export_entry = p.commonjs_named_exports.getOrPut(p.allocator, key) catch unreachable;
|
||||
if (!named_export_entry.found_existing) {
|
||||
const new_ref = p.newSymbol(
|
||||
.other,
|
||||
std.fmt.allocPrint(p.allocator, "${any}", .{bun.fmt.fmtIdentifier(key)}) catch unreachable,
|
||||
) catch unreachable;
|
||||
p.module_scope.generated.push(p.allocator, new_ref) catch unreachable;
|
||||
named_export_entry.value_ptr.* = .{
|
||||
.loc_ref = LocRef{
|
||||
.loc = name_loc,
|
||||
.ref = new_ref,
|
||||
},
|
||||
.needs_decl = false,
|
||||
};
|
||||
}
|
||||
const ref = named_export_entry.value_ptr.loc_ref.ref.?;
|
||||
// module.exports = {
|
||||
// foo: "bar",
|
||||
// baz: "qux",
|
||||
// }
|
||||
// ->
|
||||
// exports.foo = "bar", exports.baz = "qux"
|
||||
// Which will become
|
||||
// $foo = "bar";
|
||||
// $baz = "qux";
|
||||
// export { $foo as foo, $baz as baz }
|
||||
|
||||
decls[0] = .{
|
||||
.binding = p.b(B.Identifier{ .ref = ref }, prop.key.?.loc),
|
||||
.value = value,
|
||||
};
|
||||
// we have to ensure these are known to be top-level
|
||||
p.declared_symbols.append(p.allocator, .{
|
||||
.ref = ref,
|
||||
.is_top_level = true,
|
||||
}) catch unreachable;
|
||||
p.had_commonjs_named_exports_this_visit = true;
|
||||
clause_items[0] = js_ast.ClauseItem{
|
||||
// We want the generated name to not conflict
|
||||
.alias = key,
|
||||
.alias_loc = prop.key.?.loc,
|
||||
.name = named_export_entry.value_ptr.loc_ref,
|
||||
};
|
||||
|
||||
stmts.appendSlice(
|
||||
&[_]Stmt{
|
||||
p.s(
|
||||
S.Local{
|
||||
.kind = .k_var,
|
||||
.is_export = false,
|
||||
.was_commonjs_export = true,
|
||||
.decls = G.Decl.List.init(decls[0..1]),
|
||||
},
|
||||
prop.key.?.loc,
|
||||
),
|
||||
p.s(
|
||||
S.ExportClause{
|
||||
.items = clause_items[0..1],
|
||||
.is_single_line = true,
|
||||
},
|
||||
prop.key.?.loc,
|
||||
),
|
||||
},
|
||||
) catch unreachable;
|
||||
decls = decls[1..];
|
||||
clause_items = clause_items[1..];
|
||||
}
|
||||
|
||||
p.ignoreUsage(p.module_ref);
|
||||
p.commonjs_replacement_stmts = stmts.items;
|
||||
return p.newExpr(E.Missing{}, name_loc);
|
||||
}
|
||||
|
||||
// Deoptimizations:
|
||||
// delete module.exports
|
||||
// module.exports();
|
||||
if (identifier_opts.is_call_target or identifier_opts.is_delete_target or identifier_opts.assign_target != .none) {
|
||||
p.deoptimizeCommonJSNamedExports();
|
||||
return null;
|
||||
}
|
||||
|
||||
// rewrite `module.exports` to `exports`
|
||||
return .{ .data = .{ .e_special = .module_exports }, .loc = name_loc };
|
||||
} else if (p.options.bundle and strings.eqlComptime(name, "id") and identifier_opts.assign_target == .none) {
|
||||
// inline module.id
|
||||
p.ignoreUsage(p.module_ref);
|
||||
return p.newExpr(E.String.init(p.source.path.pretty), name_loc);
|
||||
} else if (p.options.bundle and strings.eqlComptime(name, "filename") and identifier_opts.assign_target == .none) {
|
||||
// inline module.filename
|
||||
p.ignoreUsage(p.module_ref);
|
||||
return p.newExpr(E.String.init(p.source.path.name.filename), name_loc);
|
||||
} else if (p.options.bundle and strings.eqlComptime(name, "path") and identifier_opts.assign_target == .none) {
|
||||
// inline module.path
|
||||
p.ignoreUsage(p.module_ref);
|
||||
return p.newExpr(E.String.init(p.source.path.pretty), name_loc);
|
||||
}
|
||||
}
|
||||
|
||||
if (p.shouldUnwrapCommonJSToESM()) {
|
||||
if (!p.is_control_flow_dead and id.ref.eql(p.exports_ref)) {
|
||||
if (!p.commonjs_named_exports_deoptimized) {
|
||||
if (identifier_opts.is_delete_target) {
|
||||
p.deoptimizeCommonJSNamedExports();
|
||||
return null;
|
||||
}
|
||||
|
||||
const named_export_entry = p.commonjs_named_exports.getOrPut(p.allocator, name) catch unreachable;
|
||||
if (!named_export_entry.found_existing) {
|
||||
const new_ref = p.newSymbol(
|
||||
.other,
|
||||
std.fmt.allocPrint(p.allocator, "${any}", .{bun.fmt.fmtIdentifier(name)}) catch unreachable,
|
||||
) catch unreachable;
|
||||
p.module_scope.generated.push(p.allocator, new_ref) catch unreachable;
|
||||
named_export_entry.value_ptr.* = .{
|
||||
.loc_ref = LocRef{
|
||||
.loc = name_loc,
|
||||
.ref = new_ref,
|
||||
},
|
||||
.needs_decl = true,
|
||||
};
|
||||
if (p.commonjs_named_exports_needs_conversion == std.math.maxInt(u32))
|
||||
p.commonjs_named_exports_needs_conversion = @as(u32, @truncate(p.commonjs_named_exports.count() - 1));
|
||||
}
|
||||
|
||||
const ref = named_export_entry.value_ptr.*.loc_ref.ref.?;
|
||||
p.ignoreUsage(id.ref);
|
||||
p.recordUsage(ref);
|
||||
|
||||
return p.newExpr(
|
||||
E.CommonJSExportIdentifier{
|
||||
.ref = ref,
|
||||
},
|
||||
name_loc,
|
||||
);
|
||||
} else if (p.options.features.commonjs_at_runtime and identifier_opts.assign_target != .none) {
|
||||
p.has_commonjs_export_names = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle references to namespaces or namespace members
|
||||
if (p.ts_namespace.expr == .e_identifier and
|
||||
id.ref.eql(p.ts_namespace.expr.e_identifier.ref) and
|
||||
identifier_opts.assign_target == .none and
|
||||
!identifier_opts.is_delete_target)
|
||||
{
|
||||
return maybeRewritePropertyAccessForNamespace(p, name, &target, loc, name_loc);
|
||||
}
|
||||
},
|
||||
.e_string => |str| {
|
||||
if (p.options.features.minify_syntax) {
|
||||
// minify "long-string".length to 11
|
||||
if (strings.eqlComptime(name, "length")) {
|
||||
if (str.javascriptLength()) |len| {
|
||||
return p.newExpr(E.Number{ .value = @floatFromInt(len) }, loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.e_inlined_enum => |ie| {
|
||||
continue :sw ie.value.data;
|
||||
},
|
||||
.e_object => |obj| {
|
||||
if (comptime FeatureFlags.inline_properties_in_transpiler) {
|
||||
if (p.options.features.minify_syntax) {
|
||||
// Rewrite a property access like this:
|
||||
// { f: () => {} }.f
|
||||
// To:
|
||||
// () => {}
|
||||
//
|
||||
// To avoid thinking too much about edgecases, only do this for:
|
||||
// 1) Objects with a single property
|
||||
// 2) Not a method, not a computed property
|
||||
if (obj.properties.len == 1 and
|
||||
!identifier_opts.is_delete_target and
|
||||
identifier_opts.assign_target == .none and !identifier_opts.is_call_target)
|
||||
{
|
||||
const prop: G.Property = obj.properties.ptr[0];
|
||||
if (prop.value != null and
|
||||
prop.flags.count() == 0 and
|
||||
prop.key != null and
|
||||
prop.key.?.data == .e_string and
|
||||
prop.key.?.data.e_string.eql([]const u8, name) and
|
||||
!bun.strings.eqlComptime(name, "__proto__"))
|
||||
{
|
||||
return prop.value.?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.e_import_meta => {
|
||||
if (strings.eqlComptime(name, "main")) {
|
||||
return p.valueForImportMetaMain(false, target.loc);
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(name, "hot")) {
|
||||
return .{ .data = .{
|
||||
.e_special = if (p.options.features.hot_module_reloading) .hot_enabled else .hot_disabled,
|
||||
}, .loc = loc };
|
||||
}
|
||||
|
||||
// Inline import.meta properties for Bake
|
||||
if (p.options.framework != null) {
|
||||
if (strings.eqlComptime(name, "dir") or strings.eqlComptime(name, "dirname")) {
|
||||
// Inline import.meta.dir
|
||||
return p.newExpr(E.String.init(p.source.path.name.dir), name_loc);
|
||||
} else if (strings.eqlComptime(name, "file")) {
|
||||
// Inline import.meta.file (filename only)
|
||||
return p.newExpr(E.String.init(p.source.path.name.filename), name_loc);
|
||||
} else if (strings.eqlComptime(name, "path")) {
|
||||
// Inline import.meta.path (full path)
|
||||
return p.newExpr(E.String.init(p.source.path.text), name_loc);
|
||||
} else if (strings.eqlComptime(name, "url")) {
|
||||
// Inline import.meta.url as file:// URL
|
||||
const bunstr = bun.String.fromBytes(p.source.path.text);
|
||||
defer bunstr.deref();
|
||||
const url = std.fmt.allocPrint(p.allocator, "{s}", .{jsc.URL.fileURLFromString(bunstr)}) catch unreachable;
|
||||
return p.newExpr(E.String.init(url), name_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")) {
|
||||
return .{ .loc = loc, .data = .e_require_main };
|
||||
}
|
||||
},
|
||||
.e_import_identifier => |id| {
|
||||
// Symbol uses due to a property access off of an imported symbol are tracked
|
||||
// specially. This lets us do tree shaking for cross-file TypeScript enums.
|
||||
if (p.options.bundle and !p.is_control_flow_dead) {
|
||||
const use = p.symbol_uses.getPtr(id.ref).?;
|
||||
use.count_estimate -|= 1;
|
||||
// note: this use is not removed as we assume it exists later
|
||||
|
||||
// Add a special symbol use instead
|
||||
const gop = p.import_symbol_property_uses.getOrPutValue(
|
||||
p.allocator,
|
||||
id.ref,
|
||||
.{},
|
||||
) catch bun.outOfMemory();
|
||||
const inner_use = gop.value_ptr.getOrPutValue(
|
||||
p.allocator,
|
||||
name,
|
||||
.{},
|
||||
) catch bun.outOfMemory();
|
||||
inner_use.value_ptr.count_estimate += 1;
|
||||
}
|
||||
},
|
||||
inline .e_dot, .e_index => |data, tag| {
|
||||
if (p.ts_namespace.expr == tag and
|
||||
data == @field(p.ts_namespace.expr, @tagName(tag)) and
|
||||
identifier_opts.assign_target == .none and
|
||||
!identifier_opts.is_delete_target)
|
||||
{
|
||||
return maybeRewritePropertyAccessForNamespace(p, name, &target, loc, name_loc);
|
||||
}
|
||||
},
|
||||
.e_special => |special| switch (special) {
|
||||
.module_exports => {
|
||||
if (p.shouldUnwrapCommonJSToESM()) {
|
||||
if (!p.is_control_flow_dead) {
|
||||
if (!p.commonjs_named_exports_deoptimized) {
|
||||
if (identifier_opts.is_delete_target) {
|
||||
p.deoptimizeCommonJSNamedExports();
|
||||
return null;
|
||||
}
|
||||
|
||||
const named_export_entry = p.commonjs_named_exports.getOrPut(p.allocator, name) catch unreachable;
|
||||
if (!named_export_entry.found_existing) {
|
||||
const new_ref = p.newSymbol(
|
||||
.other,
|
||||
std.fmt.allocPrint(p.allocator, "${any}", .{bun.fmt.fmtIdentifier(name)}) catch unreachable,
|
||||
) catch unreachable;
|
||||
p.module_scope.generated.push(p.allocator, new_ref) catch unreachable;
|
||||
named_export_entry.value_ptr.* = .{
|
||||
.loc_ref = LocRef{
|
||||
.loc = name_loc,
|
||||
.ref = new_ref,
|
||||
},
|
||||
.needs_decl = true,
|
||||
};
|
||||
if (p.commonjs_named_exports_needs_conversion == std.math.maxInt(u32))
|
||||
p.commonjs_named_exports_needs_conversion = @as(u32, @truncate(p.commonjs_named_exports.count() - 1));
|
||||
}
|
||||
|
||||
const ref = named_export_entry.value_ptr.*.loc_ref.ref.?;
|
||||
p.recordUsage(ref);
|
||||
|
||||
return p.newExpr(
|
||||
E.CommonJSExportIdentifier{
|
||||
.ref = ref,
|
||||
// Record this as from module.exports
|
||||
.base = .module_dot_exports,
|
||||
},
|
||||
name_loc,
|
||||
);
|
||||
} else if (p.options.features.commonjs_at_runtime and identifier_opts.assign_target != .none) {
|
||||
p.has_commonjs_export_names = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.hot_enabled, .hot_disabled => {
|
||||
const enabled = p.options.features.hot_module_reloading;
|
||||
if (bun.strings.eqlComptime(name, "data")) {
|
||||
return if (enabled)
|
||||
.{ .data = .{ .e_special = .hot_data }, .loc = loc }
|
||||
else
|
||||
Expr.init(E.Object, .{}, loc);
|
||||
}
|
||||
if (bun.strings.eqlComptime(name, "accept")) {
|
||||
if (!enabled) {
|
||||
p.method_call_must_be_replaced_with_undefined = true;
|
||||
return .{ .data = .e_undefined, .loc = loc };
|
||||
}
|
||||
return .{ .data = .{
|
||||
.e_special = .hot_accept,
|
||||
}, .loc = loc };
|
||||
}
|
||||
const lookup_table = comptime bun.ComptimeStringMap(void, [_]struct { [:0]const u8, void }{
|
||||
.{ "decline", {} },
|
||||
.{ "dispose", {} },
|
||||
.{ "prune", {} },
|
||||
.{ "invalidate", {} },
|
||||
.{ "on", {} },
|
||||
.{ "off", {} },
|
||||
.{ "send", {} },
|
||||
});
|
||||
if (lookup_table.has(name)) {
|
||||
if (enabled) {
|
||||
return Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.hmr_api_ref, target.loc),
|
||||
.name = name,
|
||||
.name_loc = name_loc,
|
||||
}, loc);
|
||||
} else {
|
||||
p.method_call_must_be_replaced_with_undefined = true;
|
||||
return .{ .data = .e_undefined, .loc = loc };
|
||||
}
|
||||
} else {
|
||||
// This error is a bit out of place since the HMR
|
||||
// API is validated in the parser instead of at
|
||||
// runtime. When the API is not validated in this
|
||||
// way, the developer may unintentionally read or
|
||||
// write internal fields of HMRModule.
|
||||
p.log.addError(
|
||||
p.source,
|
||||
loc,
|
||||
std.fmt.allocPrint(
|
||||
p.allocator,
|
||||
"import.meta.hot.{s} does not exist",
|
||||
.{name},
|
||||
) catch bun.outOfMemory(),
|
||||
) catch bun.outOfMemory();
|
||||
return .{ .data = .e_undefined, .loc = loc };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn maybeRewritePropertyAccessForNamespace(
|
||||
p: *P,
|
||||
name: string,
|
||||
target: *const Expr,
|
||||
loc: logger.Loc,
|
||||
name_loc: logger.Loc,
|
||||
) ?Expr {
|
||||
if (p.ts_namespace.map.?.get(name)) |value| {
|
||||
switch (value.data) {
|
||||
.enum_number => |num| {
|
||||
p.ignoreUsageOfIdentifierInDotChain(target.*);
|
||||
return p.wrapInlinedEnum(
|
||||
.{ .loc = loc, .data = .{ .e_number = .{ .value = num } } },
|
||||
name,
|
||||
);
|
||||
},
|
||||
|
||||
.enum_string => |str| {
|
||||
p.ignoreUsageOfIdentifierInDotChain(target.*);
|
||||
return p.wrapInlinedEnum(
|
||||
.{ .loc = loc, .data = .{ .e_string = str } },
|
||||
name,
|
||||
);
|
||||
},
|
||||
|
||||
.namespace => |namespace| {
|
||||
// If this isn't a constant, return a clone of this property access
|
||||
// but with the namespace member data associated with it so that
|
||||
// more property accesses off of this property access are recognized.
|
||||
const expr = if (js_lexer.isIdentifier(name))
|
||||
p.newExpr(E.Dot{
|
||||
.target = target.*,
|
||||
.name = name,
|
||||
.name_loc = name_loc,
|
||||
}, loc)
|
||||
else
|
||||
p.newExpr(E.Dot{
|
||||
.target = target.*,
|
||||
.name = name,
|
||||
.name_loc = name_loc,
|
||||
}, loc);
|
||||
|
||||
p.ts_namespace = .{
|
||||
.expr = expr.data,
|
||||
.map = namespace,
|
||||
};
|
||||
|
||||
return expr;
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn checkIfDefinedHelper(p: *P, expr: Expr) !Expr {
|
||||
return p.newExpr(
|
||||
E.Binary{
|
||||
.op = .bin_strict_eq,
|
||||
.left = p.newExpr(
|
||||
E.Unary{
|
||||
.op = .un_typeof,
|
||||
.value = expr,
|
||||
},
|
||||
logger.Loc.Empty,
|
||||
),
|
||||
.right = p.newExpr(
|
||||
E.String{ .data = "undefined" },
|
||||
logger.Loc.Empty,
|
||||
),
|
||||
},
|
||||
logger.Loc.Empty,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn maybeDefinedHelper(p: *P, identifier_expr: Expr) !Expr {
|
||||
return p.newExpr(
|
||||
E.If{
|
||||
.test_ = try p.checkIfDefinedHelper(identifier_expr),
|
||||
.yes = p.newExpr(
|
||||
E.Identifier{
|
||||
.ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref,
|
||||
},
|
||||
logger.Loc.Empty,
|
||||
),
|
||||
.no = identifier_expr,
|
||||
},
|
||||
logger.Loc.Empty,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn maybeCommaSpreadError(p: *P, _comma_after_spread: ?logger.Loc) void {
|
||||
const comma_after_spread = _comma_after_spread orelse return;
|
||||
if (comma_after_spread.start == -1) return;
|
||||
|
||||
p.log.addRangeError(p.source, logger.Range{ .loc = comma_after_spread, .len = 1 }, "Unexpected \",\" after rest pattern") catch unreachable;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const FeatureFlags = bun.FeatureFlags;
|
||||
const assert = bun.assert;
|
||||
const js_lexer = bun.js_lexer;
|
||||
const jsc = bun.jsc;
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const B = js_ast.B;
|
||||
const Binding = js_ast.Binding;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const Flags = js_ast.Flags;
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const Stmt = js_ast.Stmt;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Decl = G.Decl;
|
||||
const Property = G.Property;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const IdentifierOpts = js_parser.IdentifierOpts;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const RelocateVars = js_parser.RelocateVars;
|
||||
const SideEffects = js_parser.SideEffects;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
const options = js_parser.options;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
1396
src/ast/parse.zig
1396
src/ast/parse.zig
File diff suppressed because it is too large
Load Diff
@@ -1,517 +0,0 @@
|
||||
pub fn ParseFn(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
const is_typescript_enabled = P.is_typescript_enabled;
|
||||
|
||||
/// This assumes the "function" token has already been parsed
|
||||
pub fn parseFnStmt(noalias p: *P, loc: logger.Loc, noalias opts: *ParseStatementOptions, asyncRange: ?logger.Range) !Stmt {
|
||||
const is_generator = p.lexer.token == T.t_asterisk;
|
||||
const is_async = asyncRange != null;
|
||||
|
||||
if (is_generator) {
|
||||
// p.markSyntaxFeature(compat.Generator, p.lexer.Range())
|
||||
try p.lexer.next();
|
||||
} else if (is_async) {
|
||||
// p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator)
|
||||
}
|
||||
|
||||
switch (opts.lexical_decl) {
|
||||
.forbid => {
|
||||
try p.forbidLexicalDecl(loc);
|
||||
},
|
||||
|
||||
// Allow certain function statements in certain single-statement contexts
|
||||
.allow_fn_inside_if, .allow_fn_inside_label => {
|
||||
if (opts.is_typescript_declare or is_generator or is_async) {
|
||||
try p.forbidLexicalDecl(loc);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
var name: ?js_ast.LocRef = null;
|
||||
var nameText: string = "";
|
||||
|
||||
// The name is optional for "export default function() {}" pseudo-statements
|
||||
if (!opts.is_name_optional or p.lexer.token == T.t_identifier) {
|
||||
const nameLoc = p.lexer.loc();
|
||||
nameText = p.lexer.identifier;
|
||||
try p.lexer.expect(T.t_identifier);
|
||||
// Difference
|
||||
const ref = try p.newSymbol(Symbol.Kind.other, nameText);
|
||||
name = js_ast.LocRef{
|
||||
.loc = nameLoc,
|
||||
.ref = ref,
|
||||
};
|
||||
}
|
||||
|
||||
// Even anonymous functions can have TypeScript type parameters
|
||||
if (is_typescript_enabled) {
|
||||
_ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true });
|
||||
}
|
||||
|
||||
// Introduce a fake block scope for function declarations inside if statements
|
||||
var ifStmtScopeIndex: usize = 0;
|
||||
const hasIfScope = opts.lexical_decl == .allow_fn_inside_if;
|
||||
if (hasIfScope) {
|
||||
ifStmtScopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.block, loc);
|
||||
}
|
||||
|
||||
var scopeIndex: usize = 0;
|
||||
var pushedScopeForFunctionArgs = false;
|
||||
// Push scope if the current lexer token is an open parenthesis token.
|
||||
// That is, the parser is about parsing function arguments
|
||||
if (p.lexer.token == .t_open_paren) {
|
||||
scopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.function_args, p.lexer.loc());
|
||||
pushedScopeForFunctionArgs = true;
|
||||
}
|
||||
|
||||
var func = try p.parseFn(name, FnOrArrowDataParse{
|
||||
.needs_async_loc = loc,
|
||||
.async_range = asyncRange orelse logger.Range.None,
|
||||
.has_async_range = asyncRange != null,
|
||||
.allow_await = if (is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident,
|
||||
.allow_yield = if (is_generator) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident,
|
||||
.is_typescript_declare = opts.is_typescript_declare,
|
||||
|
||||
// Only allow omitting the body if we're parsing TypeScript
|
||||
.allow_missing_body_for_type_script = is_typescript_enabled,
|
||||
});
|
||||
p.fn_or_arrow_data_parse.has_argument_decorators = false;
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
// Don't output anything if it's just a forward declaration of a function
|
||||
if ((opts.is_typescript_declare or func.flags.contains(.is_forward_declaration)) and pushedScopeForFunctionArgs) {
|
||||
p.popAndDiscardScope(scopeIndex);
|
||||
|
||||
// Balance the fake block scope introduced above
|
||||
if (hasIfScope) {
|
||||
p.popScope();
|
||||
}
|
||||
|
||||
if (opts.is_typescript_declare and opts.is_namespace_scope and opts.is_export) {
|
||||
p.has_non_local_export_declare_inside_namespace = true;
|
||||
}
|
||||
|
||||
return p.s(S.TypeScript{}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
if (pushedScopeForFunctionArgs) {
|
||||
p.popScope();
|
||||
}
|
||||
|
||||
// Only declare the function after we know if it had a body or not. Otherwise
|
||||
// TypeScript code such as this will double-declare the symbol:
|
||||
//
|
||||
// function foo(): void;
|
||||
// function foo(): void {}
|
||||
//
|
||||
if (name != null) {
|
||||
const kind = if (is_generator or is_async)
|
||||
Symbol.Kind.generator_or_async_function
|
||||
else
|
||||
Symbol.Kind.hoisted_function;
|
||||
|
||||
name.?.ref = try p.declareSymbol(kind, name.?.loc, nameText);
|
||||
func.name = name;
|
||||
}
|
||||
|
||||
func.flags.setPresent(.has_if_scope, hasIfScope);
|
||||
func.flags.setPresent(.is_export, opts.is_export);
|
||||
|
||||
// Balance the fake block scope introduced above
|
||||
if (hasIfScope) {
|
||||
p.popScope();
|
||||
}
|
||||
|
||||
return p.s(
|
||||
S.Function{
|
||||
.func = func,
|
||||
},
|
||||
loc,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn parseFn(p: *P, name: ?js_ast.LocRef, opts: FnOrArrowDataParse) anyerror!G.Fn {
|
||||
// if data.allowAwait and data.allowYield {
|
||||
// p.markSyntaxFeature(compat.AsyncGenerator, data.asyncRange)
|
||||
// }
|
||||
|
||||
var func = G.Fn{
|
||||
.name = name,
|
||||
|
||||
.flags = Flags.Function.init(.{
|
||||
.has_rest_arg = false,
|
||||
.is_async = opts.allow_await == .allow_expr,
|
||||
.is_generator = opts.allow_yield == .allow_expr,
|
||||
}),
|
||||
|
||||
.arguments_ref = null,
|
||||
.open_parens_loc = p.lexer.loc(),
|
||||
};
|
||||
try p.lexer.expect(T.t_open_paren);
|
||||
|
||||
// Await and yield are not allowed in function arguments
|
||||
var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse);
|
||||
|
||||
p.fn_or_arrow_data_parse.allow_await = if (opts.allow_await == .allow_expr)
|
||||
AwaitOrYield.forbid_all
|
||||
else
|
||||
AwaitOrYield.allow_ident;
|
||||
|
||||
p.fn_or_arrow_data_parse.allow_yield = if (opts.allow_yield == .allow_expr)
|
||||
AwaitOrYield.forbid_all
|
||||
else
|
||||
AwaitOrYield.allow_ident;
|
||||
|
||||
// Don't suggest inserting "async" before anything if "await" is found
|
||||
p.fn_or_arrow_data_parse.needs_async_loc = logger.Loc.Empty;
|
||||
|
||||
// If "super()" is allowed in the body, it's allowed in the arguments
|
||||
p.fn_or_arrow_data_parse.allow_super_call = opts.allow_super_call;
|
||||
p.fn_or_arrow_data_parse.allow_super_property = opts.allow_super_property;
|
||||
|
||||
var rest_arg: bool = false;
|
||||
var arg_has_decorators: bool = false;
|
||||
var args = List(G.Arg){};
|
||||
while (p.lexer.token != T.t_close_paren) {
|
||||
// Skip over "this" type annotations
|
||||
if (is_typescript_enabled and p.lexer.token == T.t_this) {
|
||||
try p.lexer.next();
|
||||
if (p.lexer.token == T.t_colon) {
|
||||
try p.lexer.next();
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
}
|
||||
if (p.lexer.token != T.t_comma) {
|
||||
break;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
continue;
|
||||
}
|
||||
|
||||
var ts_decorators: []ExprNodeIndex = &([_]ExprNodeIndex{});
|
||||
if (opts.allow_ts_decorators) {
|
||||
ts_decorators = try p.parseTypeScriptDecorators();
|
||||
if (ts_decorators.len > 0) {
|
||||
arg_has_decorators = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!func.flags.contains(.has_rest_arg) and p.lexer.token == T.t_dot_dot_dot) {
|
||||
// p.markSyntaxFeature
|
||||
try p.lexer.next();
|
||||
rest_arg = true;
|
||||
func.flags.insert(.has_rest_arg);
|
||||
}
|
||||
|
||||
var is_typescript_ctor_field = false;
|
||||
const is_identifier = p.lexer.token == T.t_identifier;
|
||||
var text = p.lexer.identifier;
|
||||
var arg = try p.parseBinding(.{});
|
||||
var ts_metadata = TypeScript.Metadata.default;
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
if (is_identifier and opts.is_constructor) {
|
||||
// Skip over TypeScript accessibility modifiers, which turn this argument
|
||||
// into a class field when used inside a class constructor. This is known
|
||||
// as a "parameter property" in TypeScript.
|
||||
while (true) {
|
||||
switch (p.lexer.token) {
|
||||
.t_identifier, .t_open_brace, .t_open_bracket => {
|
||||
if (!js_lexer.TypeScriptAccessibilityModifier.has(text)) {
|
||||
break;
|
||||
}
|
||||
|
||||
is_typescript_ctor_field = true;
|
||||
|
||||
// TypeScript requires an identifier binding
|
||||
if (p.lexer.token != .t_identifier) {
|
||||
try p.lexer.expect(.t_identifier);
|
||||
}
|
||||
text = p.lexer.identifier;
|
||||
|
||||
// Re-parse the binding (the current binding is the TypeScript keyword)
|
||||
arg = try p.parseBinding(.{});
|
||||
},
|
||||
else => {
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "function foo(a?) {}"
|
||||
if (p.lexer.token == .t_question) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
// "function foo(a: any) {}"
|
||||
if (p.lexer.token == .t_colon) {
|
||||
try p.lexer.next();
|
||||
if (!rest_arg) {
|
||||
if (p.options.features.emit_decorator_metadata and
|
||||
opts.allow_ts_decorators and
|
||||
(opts.has_argument_decorators or opts.has_decorators or arg_has_decorators))
|
||||
{
|
||||
ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest);
|
||||
} else {
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
}
|
||||
} else {
|
||||
// rest parameter is always object, leave metadata as m_none
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parseStmtOpts = ParseStatementOptions{};
|
||||
p.declareBinding(.hoisted, &arg, &parseStmtOpts) catch unreachable;
|
||||
|
||||
var default_value: ?ExprNodeIndex = null;
|
||||
if (!func.flags.contains(.has_rest_arg) and p.lexer.token == .t_equals) {
|
||||
// p.markSyntaxFeature
|
||||
try p.lexer.next();
|
||||
default_value = try p.parseExpr(.comma);
|
||||
}
|
||||
|
||||
args.append(p.allocator, G.Arg{
|
||||
.ts_decorators = ExprNodeList.init(ts_decorators),
|
||||
.binding = arg,
|
||||
.default = default_value,
|
||||
|
||||
// We need to track this because it affects code generation
|
||||
.is_typescript_ctor_field = is_typescript_ctor_field,
|
||||
.ts_metadata = ts_metadata,
|
||||
}) catch unreachable;
|
||||
|
||||
if (p.lexer.token != .t_comma) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (func.flags.contains(.has_rest_arg)) {
|
||||
// JavaScript does not allow a comma after a rest argument
|
||||
if (opts.is_typescript_declare) {
|
||||
// TypeScript does allow a comma after a rest argument in a "declare" context
|
||||
try p.lexer.next();
|
||||
} else {
|
||||
try p.lexer.expect(.t_close_paren);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
rest_arg = false;
|
||||
}
|
||||
if (args.items.len > 0) {
|
||||
func.args = args.items;
|
||||
}
|
||||
|
||||
// Reserve the special name "arguments" in this scope. This ensures that it
|
||||
// shadows any variable called "arguments" in any parent scopes. But only do
|
||||
// this if it wasn't already declared above because arguments are allowed to
|
||||
// be called "arguments", in which case the real "arguments" is inaccessible.
|
||||
if (!p.current_scope.members.contains("arguments")) {
|
||||
func.arguments_ref = p.declareSymbolMaybeGenerated(.arguments, func.open_parens_loc, arguments_str, false) catch unreachable;
|
||||
p.symbols.items[func.arguments_ref.?.innerIndex()].must_not_be_renamed = true;
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_close_paren);
|
||||
p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data);
|
||||
|
||||
p.fn_or_arrow_data_parse.has_argument_decorators = arg_has_decorators;
|
||||
|
||||
// "function foo(): any {}"
|
||||
if (is_typescript_enabled) {
|
||||
if (p.lexer.token == .t_colon) {
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) {
|
||||
func.return_ts_metadata = try p.skipTypescriptReturnTypeWithMetadata();
|
||||
} else {
|
||||
try p.skipTypescriptReturnType();
|
||||
}
|
||||
} else if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) {
|
||||
if (func.flags.contains(.is_async)) {
|
||||
func.return_ts_metadata = .m_promise;
|
||||
} else {
|
||||
func.return_ts_metadata = .m_undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "function foo(): any;"
|
||||
if (opts.allow_missing_body_for_type_script and p.lexer.token != .t_open_brace) {
|
||||
try p.lexer.expectOrInsertSemicolon();
|
||||
func.flags.insert(.is_forward_declaration);
|
||||
return func;
|
||||
}
|
||||
var tempOpts = opts;
|
||||
func.body = try p.parseFnBody(&tempOpts);
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
pub fn parseFnExpr(p: *P, loc: logger.Loc, is_async: bool, async_range: logger.Range) !Expr {
|
||||
try p.lexer.next();
|
||||
const is_generator = p.lexer.token == T.t_asterisk;
|
||||
if (is_generator) {
|
||||
// p.markSyntaxFeature()
|
||||
try p.lexer.next();
|
||||
} else if (is_async) {
|
||||
// p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator)
|
||||
}
|
||||
|
||||
var name: ?js_ast.LocRef = null;
|
||||
|
||||
_ = p.pushScopeForParsePass(.function_args, loc) catch unreachable;
|
||||
|
||||
// The name is optional
|
||||
if (p.lexer.token == .t_identifier) {
|
||||
const text = p.lexer.identifier;
|
||||
|
||||
// Don't declare the name "arguments" since it's shadowed and inaccessible
|
||||
name = js_ast.LocRef{
|
||||
.loc = p.lexer.loc(),
|
||||
.ref = if (text.len > 0 and !strings.eqlComptime(text, "arguments"))
|
||||
try p.declareSymbol(.hoisted_function, p.lexer.loc(), text)
|
||||
else
|
||||
try p.newSymbol(.hoisted_function, text),
|
||||
};
|
||||
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
// Even anonymous functions can have TypeScript type parameters
|
||||
if (comptime is_typescript_enabled) {
|
||||
_ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true });
|
||||
}
|
||||
|
||||
const func = try p.parseFn(name, FnOrArrowDataParse{
|
||||
.needs_async_loc = loc,
|
||||
.async_range = async_range,
|
||||
.allow_await = if (is_async) .allow_expr else .allow_ident,
|
||||
.allow_yield = if (is_generator) .allow_expr else .allow_ident,
|
||||
});
|
||||
p.fn_or_arrow_data_parse.has_argument_decorators = false;
|
||||
|
||||
p.validateFunctionName(func, .expr);
|
||||
p.popScope();
|
||||
|
||||
return p.newExpr(js_ast.E.Function{
|
||||
.func = func,
|
||||
}, loc);
|
||||
}
|
||||
|
||||
pub fn parseFnBody(p: *P, data: *FnOrArrowDataParse) !G.FnBody {
|
||||
const oldFnOrArrowData = p.fn_or_arrow_data_parse;
|
||||
const oldAllowIn = p.allow_in;
|
||||
p.fn_or_arrow_data_parse = data.*;
|
||||
p.allow_in = true;
|
||||
|
||||
const loc = p.lexer.loc();
|
||||
var pushedScopeForFunctionBody = false;
|
||||
if (p.lexer.token == .t_open_brace) {
|
||||
_ = try p.pushScopeForParsePass(Scope.Kind.function_body, p.lexer.loc());
|
||||
pushedScopeForFunctionBody = true;
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_open_brace);
|
||||
var opts = ParseStatementOptions{};
|
||||
const stmts = try p.parseStmtsUpTo(.t_close_brace, &opts);
|
||||
try p.lexer.next();
|
||||
|
||||
if (pushedScopeForFunctionBody) p.popScope();
|
||||
|
||||
p.allow_in = oldAllowIn;
|
||||
p.fn_or_arrow_data_parse = oldFnOrArrowData;
|
||||
return G.FnBody{ .loc = loc, .stmts = stmts };
|
||||
}
|
||||
|
||||
pub fn parseArrowBody(p: *P, args: []js_ast.G.Arg, data: *FnOrArrowDataParse) !E.Arrow {
|
||||
const arrow_loc = p.lexer.loc();
|
||||
|
||||
// Newlines are not allowed before "=>"
|
||||
if (p.lexer.has_newline_before) {
|
||||
try p.log.addRangeError(p.source, p.lexer.range(), "Unexpected newline before \"=>\"");
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
try p.lexer.expect(T.t_equals_greater_than);
|
||||
|
||||
for (args) |*arg| {
|
||||
var opts = ParseStatementOptions{};
|
||||
try p.declareBinding(Symbol.Kind.hoisted, &arg.binding, &opts);
|
||||
}
|
||||
|
||||
// The ability to use "this" and "super()" is inherited by arrow functions
|
||||
data.allow_super_call = p.fn_or_arrow_data_parse.allow_super_call;
|
||||
data.allow_super_property = p.fn_or_arrow_data_parse.allow_super_property;
|
||||
data.is_this_disallowed = p.fn_or_arrow_data_parse.is_this_disallowed;
|
||||
|
||||
if (p.lexer.token == .t_open_brace) {
|
||||
const body = try p.parseFnBody(data);
|
||||
p.after_arrow_body_loc = p.lexer.loc();
|
||||
return E.Arrow{ .args = args, .body = body };
|
||||
}
|
||||
|
||||
_ = try p.pushScopeForParsePass(Scope.Kind.function_body, arrow_loc);
|
||||
defer p.popScope();
|
||||
|
||||
var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse);
|
||||
|
||||
p.fn_or_arrow_data_parse = data.*;
|
||||
const expr = try p.parseExpr(Level.comma);
|
||||
p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data);
|
||||
|
||||
var stmts = try p.allocator.alloc(Stmt, 1);
|
||||
stmts[0] = p.s(S.Return{ .value = expr }, expr.loc);
|
||||
return E.Arrow{ .args = args, .prefer_expr = true, .body = G.FnBody{ .loc = arrow_loc, .stmts = stmts } };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeIndex = js_ast.ExprNodeIndex;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const Flags = js_ast.Flags;
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const Scope = js_ast.Scope;
|
||||
const Stmt = js_ast.Stmt;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Arg = G.Arg;
|
||||
|
||||
const Op = js_ast.Op;
|
||||
const Level = js_ast.Op.Level;
|
||||
|
||||
const js_lexer = bun.js_lexer;
|
||||
const T = js_lexer.T;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const AwaitOrYield = js_parser.AwaitOrYield;
|
||||
const FnOrArrowDataParse = js_parser.FnOrArrowDataParse;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const ParseStatementOptions = js_parser.ParseStatementOptions;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
const arguments_str = js_parser.arguments_str;
|
||||
const options = js_parser.options;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
@@ -1,437 +0,0 @@
|
||||
pub fn ParseImportExport(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
const is_typescript_enabled = P.is_typescript_enabled;
|
||||
const only_scan_imports_and_do_not_visit = P.only_scan_imports_and_do_not_visit;
|
||||
|
||||
/// Note: The caller has already parsed the "import" keyword
|
||||
pub fn parseImportExpr(noalias p: *P, loc: logger.Loc, level: Level) anyerror!Expr {
|
||||
// Parse an "import.meta" expression
|
||||
if (p.lexer.token == .t_dot) {
|
||||
p.esm_import_keyword = js_lexer.rangeOfIdentifier(p.source, loc);
|
||||
try p.lexer.next();
|
||||
if (p.lexer.isContextualKeyword("meta")) {
|
||||
try p.lexer.next();
|
||||
p.has_import_meta = true;
|
||||
return p.newExpr(E.ImportMeta{}, loc);
|
||||
} else {
|
||||
try p.lexer.expectedString("\"meta\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (level.gt(.call)) {
|
||||
const r = js_lexer.rangeOfIdentifier(p.source, loc);
|
||||
p.log.addRangeError(p.source, r, "Cannot use an \"import\" expression here without parentheses") catch unreachable;
|
||||
}
|
||||
|
||||
// allow "in" inside call arguments;
|
||||
const old_allow_in = p.allow_in;
|
||||
p.allow_in = true;
|
||||
|
||||
p.lexer.preserve_all_comments_before = true;
|
||||
try p.lexer.expect(.t_open_paren);
|
||||
|
||||
// const comments = try p.lexer.comments_to_preserve_before.toOwnedSlice();
|
||||
p.lexer.comments_to_preserve_before.clearRetainingCapacity();
|
||||
|
||||
p.lexer.preserve_all_comments_before = false;
|
||||
|
||||
const value = try p.parseExpr(.comma);
|
||||
|
||||
var import_options = Expr.empty;
|
||||
if (p.lexer.token == .t_comma) {
|
||||
// "import('./foo.json', )"
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.token != .t_close_paren) {
|
||||
// "import('./foo.json', { assert: { type: 'json' } })"
|
||||
import_options = try p.parseExpr(.comma);
|
||||
|
||||
if (p.lexer.token == .t_comma) {
|
||||
// "import('./foo.json', { assert: { type: 'json' } }, )"
|
||||
try p.lexer.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_close_paren);
|
||||
|
||||
p.allow_in = old_allow_in;
|
||||
|
||||
if (comptime only_scan_imports_and_do_not_visit) {
|
||||
if (value.data == .e_string and value.data.e_string.isUTF8() and value.data.e_string.isPresent()) {
|
||||
const import_record_index = p.addImportRecord(.dynamic, value.loc, value.data.e_string.slice(p.allocator));
|
||||
|
||||
return p.newExpr(E.Import{
|
||||
.expr = value,
|
||||
// .leading_interior_comments = comments,
|
||||
.import_record_index = import_record_index,
|
||||
.options = import_options,
|
||||
}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
// _ = comments; // TODO: leading_interior comments
|
||||
|
||||
return p.newExpr(E.Import{
|
||||
.expr = value,
|
||||
// .leading_interior_comments = comments,
|
||||
.import_record_index = std.math.maxInt(u32),
|
||||
.options = import_options,
|
||||
}, loc);
|
||||
}
|
||||
|
||||
pub fn parseImportClause(
|
||||
p: *P,
|
||||
) !ImportClause {
|
||||
var items = ListManaged(js_ast.ClauseItem).init(p.allocator);
|
||||
try p.lexer.expect(.t_open_brace);
|
||||
var is_single_line = !p.lexer.has_newline_before;
|
||||
// this variable should not exist if we're not in a typescript file
|
||||
var had_type_only_imports = if (comptime is_typescript_enabled)
|
||||
false;
|
||||
|
||||
while (p.lexer.token != .t_close_brace) {
|
||||
// The alias may be a keyword;
|
||||
const isIdentifier = p.lexer.token == .t_identifier;
|
||||
const alias_loc = p.lexer.loc();
|
||||
const alias = try p.parseClauseAlias("import");
|
||||
var name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(alias) };
|
||||
var original_name = alias;
|
||||
try p.lexer.next();
|
||||
|
||||
const probably_type_only_import = if (comptime is_typescript_enabled)
|
||||
strings.eqlComptime(alias, "type") and
|
||||
p.lexer.token != .t_comma and
|
||||
p.lexer.token != .t_close_brace
|
||||
else
|
||||
false;
|
||||
|
||||
// "import { type xx } from 'mod'"
|
||||
// "import { type xx as yy } from 'mod'"
|
||||
// "import { type 'xx' as yy } from 'mod'"
|
||||
// "import { type as } from 'mod'"
|
||||
// "import { type as as } from 'mod'"
|
||||
// "import { type as as as } from 'mod'"
|
||||
if (probably_type_only_import) {
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
try p.lexer.next();
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
original_name = p.lexer.identifier;
|
||||
name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(original_name) };
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.token == .t_identifier) {
|
||||
|
||||
// "import { type as as as } from 'mod'"
|
||||
// "import { type as as foo } from 'mod'"
|
||||
had_type_only_imports = true;
|
||||
try p.lexer.next();
|
||||
} else {
|
||||
// "import { type as as } from 'mod'"
|
||||
|
||||
try items.append(.{
|
||||
.alias = alias,
|
||||
.alias_loc = alias_loc,
|
||||
.name = name,
|
||||
.original_name = original_name,
|
||||
});
|
||||
}
|
||||
} else if (p.lexer.token == .t_identifier) {
|
||||
had_type_only_imports = true;
|
||||
|
||||
// "import { type as xxx } from 'mod'"
|
||||
original_name = p.lexer.identifier;
|
||||
name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(original_name) };
|
||||
try p.lexer.expect(.t_identifier);
|
||||
|
||||
if (isEvalOrArguments(original_name)) {
|
||||
const r = p.source.rangeOfString(name.loc);
|
||||
try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot use {s} as an identifier here", .{original_name});
|
||||
}
|
||||
|
||||
try items.append(.{
|
||||
.alias = alias,
|
||||
.alias_loc = alias_loc,
|
||||
.name = name,
|
||||
.original_name = original_name,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const is_identifier = p.lexer.token == .t_identifier;
|
||||
|
||||
// "import { type xx } from 'mod'"
|
||||
// "import { type xx as yy } from 'mod'"
|
||||
// "import { type if as yy } from 'mod'"
|
||||
// "import { type 'xx' as yy } from 'mod'"
|
||||
_ = try p.parseClauseAlias("import");
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
try p.lexer.next();
|
||||
|
||||
try p.lexer.expect(.t_identifier);
|
||||
} else if (!is_identifier) {
|
||||
// An import where the name is a keyword must have an alias
|
||||
try p.lexer.expectedString("\"as\"");
|
||||
}
|
||||
had_type_only_imports = true;
|
||||
}
|
||||
} else {
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
try p.lexer.next();
|
||||
original_name = p.lexer.identifier;
|
||||
name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(original_name) };
|
||||
try p.lexer.expect(.t_identifier);
|
||||
} else if (!isIdentifier) {
|
||||
// An import where the name is a keyword must have an alias
|
||||
try p.lexer.expectedString("\"as\"");
|
||||
}
|
||||
|
||||
// Reject forbidden names
|
||||
if (isEvalOrArguments(original_name)) {
|
||||
const r = js_lexer.rangeOfIdentifier(p.source, name.loc);
|
||||
try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot use \"{s}\" as an identifier here", .{original_name});
|
||||
}
|
||||
|
||||
try items.append(js_ast.ClauseItem{
|
||||
.alias = alias,
|
||||
.alias_loc = alias_loc,
|
||||
.name = name,
|
||||
.original_name = original_name,
|
||||
});
|
||||
}
|
||||
|
||||
if (p.lexer.token != .t_comma) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_close_brace);
|
||||
return ImportClause{
|
||||
.items = items.items,
|
||||
.is_single_line = is_single_line,
|
||||
.had_type_only_imports = if (comptime is_typescript_enabled)
|
||||
had_type_only_imports
|
||||
else
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parseExportClause(p: *P) !ExportClauseResult {
|
||||
var items = ListManaged(js_ast.ClauseItem).initCapacity(p.allocator, 1) catch unreachable;
|
||||
try p.lexer.expect(.t_open_brace);
|
||||
var is_single_line = !p.lexer.has_newline_before;
|
||||
var first_non_identifier_loc = logger.Loc{ .start = 0 };
|
||||
var had_type_only_exports = false;
|
||||
|
||||
while (p.lexer.token != .t_close_brace) {
|
||||
var alias = try p.parseClauseAlias("export");
|
||||
var alias_loc = p.lexer.loc();
|
||||
|
||||
const name = LocRef{
|
||||
.loc = alias_loc,
|
||||
.ref = p.storeNameInRef(alias) catch unreachable,
|
||||
};
|
||||
const original_name = alias;
|
||||
|
||||
// The name can actually be a keyword if we're really an "export from"
|
||||
// statement. However, we won't know until later. Allow keywords as
|
||||
// identifiers for now and throw an error later if there's no "from".
|
||||
//
|
||||
// // This is fine
|
||||
// export { default } from 'path'
|
||||
//
|
||||
// // This is a syntax error
|
||||
// export { default }
|
||||
//
|
||||
if (p.lexer.token != .t_identifier and first_non_identifier_loc.start == 0) {
|
||||
first_non_identifier_loc = p.lexer.loc();
|
||||
}
|
||||
try p.lexer.next();
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
if (strings.eqlComptime(alias, "type") and p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) {
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
alias = try p.parseClauseAlias("export");
|
||||
alias_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) {
|
||||
// "export { type as as as }"
|
||||
// "export { type as as foo }"
|
||||
// "export { type as as 'foo' }"
|
||||
_ = p.parseClauseAlias("export") catch "";
|
||||
had_type_only_exports = true;
|
||||
try p.lexer.next();
|
||||
} else {
|
||||
// "export { type as as }"
|
||||
items.append(js_ast.ClauseItem{
|
||||
.alias = alias,
|
||||
.alias_loc = alias_loc,
|
||||
.name = name,
|
||||
.original_name = original_name,
|
||||
}) catch unreachable;
|
||||
}
|
||||
} else if (p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) {
|
||||
// "export { type as xxx }"
|
||||
// "export { type as 'xxx' }"
|
||||
alias = try p.parseClauseAlias("export");
|
||||
alias_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
items.append(js_ast.ClauseItem{
|
||||
.alias = alias,
|
||||
.alias_loc = alias_loc,
|
||||
.name = name,
|
||||
.original_name = original_name,
|
||||
}) catch unreachable;
|
||||
} else {
|
||||
had_type_only_exports = true;
|
||||
}
|
||||
} else {
|
||||
// The name can actually be a keyword if we're really an "export from"
|
||||
// statement. However, we won't know until later. Allow keywords as
|
||||
// identifiers for now and throw an error later if there's no "from".
|
||||
//
|
||||
// // This is fine
|
||||
// export { default } from 'path'
|
||||
//
|
||||
// // This is a syntax error
|
||||
// export { default }
|
||||
//
|
||||
if (p.lexer.token != .t_identifier and first_non_identifier_loc.start == 0) {
|
||||
first_non_identifier_loc = p.lexer.loc();
|
||||
}
|
||||
|
||||
// "export { type xx }"
|
||||
// "export { type xx as yy }"
|
||||
// "export { type xx as if }"
|
||||
// "export { type default } from 'path'"
|
||||
// "export { type default as if } from 'path'"
|
||||
// "export { type xx as 'yy' }"
|
||||
// "export { type 'xx' } from 'mod'"
|
||||
_ = p.parseClauseAlias("export") catch "";
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
try p.lexer.next();
|
||||
_ = p.parseClauseAlias("export") catch "";
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
had_type_only_exports = true;
|
||||
}
|
||||
} else {
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
try p.lexer.next();
|
||||
alias = try p.parseClauseAlias("export");
|
||||
alias_loc = p.lexer.loc();
|
||||
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
items.append(js_ast.ClauseItem{
|
||||
.alias = alias,
|
||||
.alias_loc = alias_loc,
|
||||
.name = name,
|
||||
.original_name = original_name,
|
||||
}) catch unreachable;
|
||||
}
|
||||
} else {
|
||||
if (p.lexer.isContextualKeyword("as")) {
|
||||
try p.lexer.next();
|
||||
alias = try p.parseClauseAlias("export");
|
||||
alias_loc = p.lexer.loc();
|
||||
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
items.append(js_ast.ClauseItem{
|
||||
.alias = alias,
|
||||
.alias_loc = alias_loc,
|
||||
.name = name,
|
||||
.original_name = original_name,
|
||||
}) catch unreachable;
|
||||
}
|
||||
|
||||
// we're done if there's no comma
|
||||
if (p.lexer.token != .t_comma) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
try p.lexer.next();
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
try p.lexer.expect(.t_close_brace);
|
||||
|
||||
// Throw an error here if we found a keyword earlier and this isn't an
|
||||
// "export from" statement after all
|
||||
if (first_non_identifier_loc.start != 0 and !p.lexer.isContextualKeyword("from")) {
|
||||
const r = js_lexer.rangeOfIdentifier(p.source, first_non_identifier_loc);
|
||||
try p.lexer.addRangeError(r, "Expected identifier but found \"{s}\"", .{p.source.textForRange(r)}, true);
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return ExportClauseResult{
|
||||
.clauses = items.items,
|
||||
.is_single_line = is_single_line,
|
||||
.had_type_only_exports = had_type_only_exports,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const assert = bun.assert;
|
||||
const js_lexer = bun.js_lexer;
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const LocRef = js_ast.LocRef;
|
||||
|
||||
const Op = js_ast.Op;
|
||||
const Level = js_ast.Op.Level;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const ExportClauseResult = js_parser.ExportClauseResult;
|
||||
const ImportClause = js_parser.ImportClause;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const isEvalOrArguments = js_parser.isEvalOrArguments;
|
||||
const options = js_parser.options;
|
||||
|
||||
const std = @import("std");
|
||||
const ListManaged = std.ArrayList;
|
||||
@@ -1,319 +0,0 @@
|
||||
pub fn ParseJSXElement(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
const is_typescript_enabled = P.is_typescript_enabled;
|
||||
const only_scan_imports_and_do_not_visit = P.only_scan_imports_and_do_not_visit;
|
||||
|
||||
pub fn parseJSXElement(noalias p: *P, loc: logger.Loc) anyerror!Expr {
|
||||
if (only_scan_imports_and_do_not_visit) {
|
||||
p.needs_jsx_import = true;
|
||||
}
|
||||
|
||||
const tag = try JSXTag.parse(P, p);
|
||||
|
||||
// The tag may have TypeScript type arguments: "<Foo<T>/>"
|
||||
if (is_typescript_enabled) {
|
||||
// Pass a flag to the type argument skipper because we need to call
|
||||
_ = try p.skipTypeScriptTypeArguments(true);
|
||||
}
|
||||
|
||||
var previous_string_with_backslash_loc = logger.Loc{};
|
||||
var properties = G.Property.List{};
|
||||
var key_prop_i: i32 = -1;
|
||||
var flags = Flags.JSXElement.Bitset{};
|
||||
var start_tag: ?ExprNodeIndex = null;
|
||||
|
||||
// Fragments don't have props
|
||||
// Fragments of the form "React.Fragment" are not parsed as fragments.
|
||||
if (@as(JSXTag.TagType, tag.data) == .tag) {
|
||||
start_tag = tag.data.tag;
|
||||
|
||||
var spread_loc: logger.Loc = logger.Loc.Empty;
|
||||
var props = ListManaged(G.Property).init(p.allocator);
|
||||
var first_spread_prop_i: i32 = -1;
|
||||
var i: i32 = 0;
|
||||
parse_attributes: while (true) {
|
||||
switch (p.lexer.token) {
|
||||
.t_identifier => {
|
||||
defer i += 1;
|
||||
// Parse the prop name
|
||||
const key_range = p.lexer.range();
|
||||
const prop_name_literal = p.lexer.identifier;
|
||||
const special_prop = E.JSXElement.SpecialProp.Map.get(prop_name_literal) orelse E.JSXElement.SpecialProp.any;
|
||||
try p.lexer.nextInsideJSXElement();
|
||||
|
||||
if (special_prop == .key) {
|
||||
// <ListItem key>
|
||||
if (p.lexer.token != .t_equals) {
|
||||
// Unlike Babel, we're going to just warn here and move on.
|
||||
try p.log.addWarning(p.source, key_range.loc, "\"key\" prop ignored. Must be a string, number or symbol.");
|
||||
continue;
|
||||
}
|
||||
|
||||
key_prop_i = i;
|
||||
}
|
||||
|
||||
const prop_name = p.newExpr(E.String{ .data = prop_name_literal }, key_range.loc);
|
||||
|
||||
// Parse the value
|
||||
var value: Expr = undefined;
|
||||
if (p.lexer.token != .t_equals) {
|
||||
|
||||
// Implicitly true value
|
||||
// <button selected>
|
||||
value = p.newExpr(E.Boolean{ .value = true }, logger.Loc{ .start = key_range.loc.start + key_range.len });
|
||||
} else {
|
||||
value = try p.parseJSXPropValueIdentifier(&previous_string_with_backslash_loc);
|
||||
}
|
||||
|
||||
try props.append(G.Property{ .key = prop_name, .value = value });
|
||||
},
|
||||
.t_open_brace => {
|
||||
defer i += 1;
|
||||
// Use Next() not ExpectInsideJSXElement() so we can parse "..."
|
||||
try p.lexer.next();
|
||||
|
||||
switch (p.lexer.token) {
|
||||
.t_dot_dot_dot => {
|
||||
try p.lexer.next();
|
||||
|
||||
if (first_spread_prop_i == -1) first_spread_prop_i = i;
|
||||
spread_loc = p.lexer.loc();
|
||||
try props.append(G.Property{ .value = try p.parseExpr(.comma), .kind = .spread });
|
||||
},
|
||||
// This implements
|
||||
// <div {foo} />
|
||||
// ->
|
||||
// <div foo={foo} />
|
||||
T.t_identifier => {
|
||||
// we need to figure out what the key they mean is
|
||||
// to do that, we must determine the key name
|
||||
const expr = try p.parseExpr(Level.lowest);
|
||||
|
||||
const key = brk: {
|
||||
switch (expr.data) {
|
||||
.e_import_identifier => |ident| {
|
||||
break :brk p.newExpr(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc);
|
||||
},
|
||||
.e_commonjs_export_identifier => |ident| {
|
||||
break :brk p.newExpr(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc);
|
||||
},
|
||||
.e_identifier => |ident| {
|
||||
break :brk p.newExpr(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc);
|
||||
},
|
||||
.e_dot => |dot| {
|
||||
break :brk p.newExpr(E.String{ .data = dot.name }, dot.name_loc);
|
||||
},
|
||||
.e_index => |index| {
|
||||
if (index.index.data == .e_string) {
|
||||
break :brk index.index;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
// If we get here, it's invalid
|
||||
try p.log.addError(p.source, expr.loc, "Invalid JSX prop shorthand, must be identifier, dot or string");
|
||||
return error.SyntaxError;
|
||||
};
|
||||
|
||||
try props.append(G.Property{ .value = expr, .key = key, .kind = .normal });
|
||||
},
|
||||
// This implements
|
||||
// <div {"foo"} />
|
||||
// <div {'foo'} />
|
||||
// ->
|
||||
// <div foo="foo" />
|
||||
// note: template literals are not supported, operations on strings are not supported either
|
||||
T.t_string_literal => {
|
||||
const key = p.newExpr(try p.lexer.toEString(), p.lexer.loc());
|
||||
try p.lexer.next();
|
||||
try props.append(G.Property{ .value = key, .key = key, .kind = .normal });
|
||||
},
|
||||
|
||||
else => try p.lexer.unexpected(),
|
||||
}
|
||||
|
||||
try p.lexer.nextInsideJSXElement();
|
||||
},
|
||||
else => {
|
||||
break :parse_attributes;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const is_key_after_spread = key_prop_i > -1 and first_spread_prop_i > -1 and key_prop_i > first_spread_prop_i;
|
||||
flags.setPresent(.is_key_after_spread, is_key_after_spread);
|
||||
properties = G.Property.List.fromList(props);
|
||||
if (is_key_after_spread and p.options.jsx.runtime == .automatic and !p.has_classic_runtime_warned) {
|
||||
try p.log.addWarning(p.source, spread_loc, "\"key\" prop after a {...spread} is deprecated in JSX. Falling back to classic runtime.");
|
||||
p.has_classic_runtime_warned = true;
|
||||
}
|
||||
}
|
||||
|
||||
// People sometimes try to use the output of "JSON.stringify()" as a JSX
|
||||
// attribute when automatically-generating JSX code. Doing so is incorrect
|
||||
// because JSX strings work like XML instead of like JS (since JSX is XML-in-
|
||||
// JS). Specifically, using a backslash before a quote does not cause it to
|
||||
// be escaped:
|
||||
//
|
||||
// JSX ends the "content" attribute here and sets "content" to 'some so-called \\'
|
||||
// v
|
||||
// <Button content="some so-called \"button text\"" />
|
||||
// ^
|
||||
// There is no "=" after the JSX attribute "text", so we expect a ">"
|
||||
//
|
||||
// This code special-cases this error to provide a less obscure error message.
|
||||
if (p.lexer.token == .t_syntax_error and strings.eqlComptime(p.lexer.raw(), "\\") and previous_string_with_backslash_loc.start > 0) {
|
||||
const r = p.lexer.range();
|
||||
// Not dealing with this right now.
|
||||
try p.log.addRangeError(p.source, r, "Invalid JSX escape - use XML entity codes quotes or pass a JavaScript string instead");
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
// A slash here is a self-closing element
|
||||
if (p.lexer.token == .t_slash) {
|
||||
const close_tag_loc = p.lexer.loc();
|
||||
// Use NextInsideJSXElement() not Next() so we can parse ">>" as ">"
|
||||
|
||||
try p.lexer.nextInsideJSXElement();
|
||||
|
||||
if (p.lexer.token != .t_greater_than) {
|
||||
try p.lexer.expected(.t_greater_than);
|
||||
}
|
||||
|
||||
return p.newExpr(E.JSXElement{
|
||||
.tag = start_tag,
|
||||
.properties = properties,
|
||||
.key_prop_index = key_prop_i,
|
||||
.flags = flags,
|
||||
.close_tag_loc = close_tag_loc,
|
||||
}, loc);
|
||||
}
|
||||
|
||||
// Use ExpectJSXElementChild() so we parse child strings
|
||||
try p.lexer.expectJSXElementChild(.t_greater_than);
|
||||
var children = ListManaged(Expr).init(p.allocator);
|
||||
// var last_element_i: usize = 0;
|
||||
|
||||
while (true) {
|
||||
switch (p.lexer.token) {
|
||||
.t_string_literal => {
|
||||
try children.append(p.newExpr(try p.lexer.toEString(), loc));
|
||||
try p.lexer.nextJSXElementChild();
|
||||
},
|
||||
.t_open_brace => {
|
||||
// Use Next() instead of NextJSXElementChild() here since the next token is an expression
|
||||
try p.lexer.next();
|
||||
|
||||
const is_spread = p.lexer.token == .t_dot_dot_dot;
|
||||
if (is_spread) {
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
// The expression is optional, and may be absent
|
||||
if (p.lexer.token != .t_close_brace) {
|
||||
var item = try p.parseExpr(.lowest);
|
||||
if (is_spread) {
|
||||
item = p.newExpr(E.Spread{ .value = item }, loc);
|
||||
}
|
||||
try children.append(item);
|
||||
}
|
||||
|
||||
// Use ExpectJSXElementChild() so we parse child strings
|
||||
try p.lexer.expectJSXElementChild(.t_close_brace);
|
||||
},
|
||||
.t_less_than => {
|
||||
const less_than_loc = p.lexer.loc();
|
||||
try p.lexer.nextInsideJSXElement();
|
||||
|
||||
if (p.lexer.token != .t_slash) {
|
||||
// This is a child element
|
||||
|
||||
children.append(try p.parseJSXElement(less_than_loc)) catch unreachable;
|
||||
|
||||
// The call to parseJSXElement() above doesn't consume the last
|
||||
// TGreaterThan because the caller knows what Next() function to call.
|
||||
// Use NextJSXElementChild() here since the next token is an element
|
||||
// child.
|
||||
try p.lexer.nextJSXElementChild();
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is the closing element
|
||||
try p.lexer.nextInsideJSXElement();
|
||||
const end_tag = try JSXTag.parse(P, p);
|
||||
|
||||
if (!strings.eql(end_tag.name, tag.name)) {
|
||||
try p.log.addRangeErrorFmtWithNote(
|
||||
p.source,
|
||||
end_tag.range,
|
||||
p.allocator,
|
||||
"Expected closing JSX tag to match opening tag \"\\<{s}\\>\"",
|
||||
.{tag.name},
|
||||
"Opening tag here:",
|
||||
.{},
|
||||
tag.range,
|
||||
);
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
if (p.lexer.token != .t_greater_than) {
|
||||
try p.lexer.expected(.t_greater_than);
|
||||
}
|
||||
|
||||
return p.newExpr(E.JSXElement{
|
||||
.tag = end_tag.data.asExpr(),
|
||||
.children = ExprNodeList.fromList(children),
|
||||
.properties = properties,
|
||||
.key_prop_index = key_prop_i,
|
||||
.flags = flags,
|
||||
.close_tag_loc = end_tag.range.loc,
|
||||
}, loc);
|
||||
},
|
||||
else => {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeIndex = js_ast.ExprNodeIndex;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const Flags = js_ast.Flags;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Property = G.Property;
|
||||
|
||||
const Op = js_ast.Op;
|
||||
const Level = js_ast.Op.Level;
|
||||
|
||||
const js_lexer = bun.js_lexer;
|
||||
const T = js_lexer.T;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const JSXTag = js_parser.JSXTag;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
const options = js_parser.options;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
const ListManaged = std.ArrayList;
|
||||
const Map = std.AutoHashMapUnmanaged;
|
||||
@@ -1,763 +0,0 @@
|
||||
pub fn ParsePrefix(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
const is_jsx_enabled = P.is_jsx_enabled;
|
||||
const is_typescript_enabled = P.is_typescript_enabled;
|
||||
|
||||
fn t_super(noalias p: *P, level: Level) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
const l = @intFromEnum(level);
|
||||
const superRange = p.lexer.range();
|
||||
try p.lexer.next();
|
||||
|
||||
switch (p.lexer.token) {
|
||||
.t_open_paren => {
|
||||
if (l < @intFromEnum(Level.call) and p.fn_or_arrow_data_parse.allow_super_call) {
|
||||
return p.newExpr(E.Super{}, loc);
|
||||
}
|
||||
},
|
||||
.t_dot, .t_open_bracket => {
|
||||
if (p.fn_or_arrow_data_parse.allow_super_property) {
|
||||
return p.newExpr(E.Super{}, loc);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
p.log.addRangeError(p.source, superRange, "Unexpected \"super\"") catch unreachable;
|
||||
return p.newExpr(E.Super{}, loc);
|
||||
}
|
||||
fn t_open_paren(noalias p: *P, level: Level) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
// Arrow functions aren't allowed in the middle of expressions
|
||||
if (level.gt(.assign)) {
|
||||
// Allow "in" inside parentheses
|
||||
const oldAllowIn = p.allow_in;
|
||||
p.allow_in = true;
|
||||
|
||||
var value = try p.parseExpr(Level.lowest);
|
||||
p.markExprAsParenthesized(&value);
|
||||
try p.lexer.expect(.t_close_paren);
|
||||
|
||||
p.allow_in = oldAllowIn;
|
||||
return value;
|
||||
}
|
||||
|
||||
return p.parseParenExpr(loc, level, ParenExprOpts{});
|
||||
}
|
||||
fn t_false(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
return p.newExpr(E.Boolean{ .value = false }, loc);
|
||||
}
|
||||
fn t_true(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
return p.newExpr(E.Boolean{ .value = true }, loc);
|
||||
}
|
||||
fn t_null(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
return p.newExpr(E.Null{}, loc);
|
||||
}
|
||||
fn t_this(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
if (p.fn_or_arrow_data_parse.is_this_disallowed) {
|
||||
p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"this\" here") catch unreachable;
|
||||
}
|
||||
try p.lexer.next();
|
||||
return Expr{ .data = Prefill.Data.This, .loc = loc };
|
||||
}
|
||||
fn t_private_identifier(noalias p: *P, level: Level) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
if (!p.allow_private_identifiers or !p.allow_in or level.gte(.compare)) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
const name = p.lexer.identifier;
|
||||
try p.lexer.next();
|
||||
|
||||
// Check for "#foo in bar"
|
||||
if (p.lexer.token != .t_in) {
|
||||
try p.lexer.expected(.t_in);
|
||||
}
|
||||
|
||||
return p.newExpr(E.PrivateIdentifier{ .ref = try p.storeNameInRef(name) }, loc);
|
||||
}
|
||||
fn t_identifier(noalias p: *P, level: Level) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
const name = p.lexer.identifier;
|
||||
const name_range = p.lexer.range();
|
||||
const raw = p.lexer.raw();
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
// Handle async and await expressions
|
||||
switch (AsyncPrefixExpression.find(name)) {
|
||||
.is_async => {
|
||||
if ((raw.ptr == name.ptr and raw.len == name.len) or AsyncPrefixExpression.find(raw) == .is_async) {
|
||||
return try p.parseAsyncPrefixExpr(name_range, level);
|
||||
}
|
||||
},
|
||||
|
||||
.is_await => {
|
||||
switch (p.fn_or_arrow_data_parse.allow_await) {
|
||||
.forbid_all => {
|
||||
p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be used here") catch unreachable;
|
||||
},
|
||||
.allow_expr => {
|
||||
if (AsyncPrefixExpression.find(raw) != .is_await) {
|
||||
p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be escaped") catch unreachable;
|
||||
} else {
|
||||
if (p.fn_or_arrow_data_parse.is_top_level) {
|
||||
p.top_level_await_keyword = name_range;
|
||||
}
|
||||
|
||||
if (p.fn_or_arrow_data_parse.track_arrow_arg_errors) {
|
||||
p.fn_or_arrow_data_parse.arrow_arg_errors.invalid_expr_await = name_range;
|
||||
}
|
||||
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == T.t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return p.newExpr(E.Await{ .value = value }, loc);
|
||||
}
|
||||
},
|
||||
.allow_ident => {
|
||||
p.lexer.prev_token_was_await_keyword = true;
|
||||
p.lexer.await_keyword_loc = name_range.loc;
|
||||
p.lexer.fn_or_arrow_start_loc = p.fn_or_arrow_data_parse.needs_async_loc;
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
.is_yield => {
|
||||
switch (p.fn_or_arrow_data_parse.allow_yield) {
|
||||
.forbid_all => {
|
||||
p.log.addRangeError(p.source, name_range, "The keyword \"yield\" cannot be used here") catch unreachable;
|
||||
},
|
||||
.allow_expr => {
|
||||
if (AsyncPrefixExpression.find(raw) != .is_yield) {
|
||||
p.log.addRangeError(p.source, name_range, "The keyword \"yield\" cannot be escaped") catch unreachable;
|
||||
} else {
|
||||
if (level.gt(.assign)) {
|
||||
p.log.addRangeError(p.source, name_range, "Cannot use a \"yield\" here without parentheses") catch unreachable;
|
||||
}
|
||||
|
||||
if (p.fn_or_arrow_data_parse.track_arrow_arg_errors) {
|
||||
p.fn_or_arrow_data_parse.arrow_arg_errors.invalid_expr_yield = name_range;
|
||||
}
|
||||
|
||||
return p.parseYieldExpr(loc);
|
||||
}
|
||||
},
|
||||
// .allow_ident => {
|
||||
|
||||
// },
|
||||
else => {
|
||||
// Try to gracefully recover if "yield" is used in the wrong place
|
||||
if (!p.lexer.has_newline_before) {
|
||||
switch (p.lexer.token) {
|
||||
.t_null, .t_identifier, .t_false, .t_true, .t_numeric_literal, .t_big_integer_literal, .t_string_literal => {
|
||||
p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" outside a generator function") catch unreachable;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
.none => {},
|
||||
}
|
||||
|
||||
// Handle the start of an arrow expression
|
||||
if (p.lexer.token == .t_equals_greater_than and level.lte(.assign)) {
|
||||
const ref = p.storeNameInRef(name) catch unreachable;
|
||||
var args = p.allocator.alloc(Arg, 1) catch unreachable;
|
||||
args[0] = Arg{ .binding = p.b(B.Identifier{
|
||||
.ref = ref,
|
||||
}, loc) };
|
||||
|
||||
_ = p.pushScopeForParsePass(.function_args, loc) catch unreachable;
|
||||
defer p.popScope();
|
||||
|
||||
var fn_or_arrow_data = FnOrArrowDataParse{
|
||||
.needs_async_loc = loc,
|
||||
};
|
||||
return p.newExpr(try p.parseArrowBody(args, &fn_or_arrow_data), loc);
|
||||
}
|
||||
|
||||
const ref = p.storeNameInRef(name) catch unreachable;
|
||||
|
||||
return Expr.initIdentifier(ref, loc);
|
||||
}
|
||||
fn t_template_head(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
const head = try p.lexer.toEString();
|
||||
|
||||
const parts = try p.parseTemplateParts(false);
|
||||
|
||||
// Check if TemplateLiteral is unsupported. We don't care for this product.`
|
||||
// if ()
|
||||
|
||||
return p.newExpr(E.Template{
|
||||
.head = .{ .cooked = head },
|
||||
.parts = parts,
|
||||
}, loc);
|
||||
}
|
||||
fn t_numeric_literal(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
const value = p.newExpr(E.Number{ .value = p.lexer.number }, loc);
|
||||
// p.checkForLegacyOctalLiteral()
|
||||
try p.lexer.next();
|
||||
return value;
|
||||
}
|
||||
fn t_big_integer_literal(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
const value = p.lexer.identifier;
|
||||
// markSyntaxFeature bigInt
|
||||
try p.lexer.next();
|
||||
return p.newExpr(E.BigInt{ .value = value }, loc);
|
||||
}
|
||||
fn t_slash(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.scanRegExp();
|
||||
// always set regex_flags_start to null to make sure we don't accidentally use the wrong value later
|
||||
defer p.lexer.regex_flags_start = null;
|
||||
const value = p.lexer.raw();
|
||||
try p.lexer.next();
|
||||
|
||||
return p.newExpr(E.RegExp{ .value = value, .flags_offset = p.lexer.regex_flags_start }, loc);
|
||||
}
|
||||
fn t_void(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == .t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return p.newExpr(E.Unary{
|
||||
.op = .un_void,
|
||||
.value = value,
|
||||
}, loc);
|
||||
}
|
||||
fn t_typeof(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == .t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return p.newExpr(E.Unary{ .op = .un_typeof, .value = value }, loc);
|
||||
}
|
||||
fn t_delete(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == .t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
if (value.data == .e_index) {
|
||||
if (value.data.e_index.index.data == .e_private_identifier) {
|
||||
const private = value.data.e_index.index.data.e_private_identifier;
|
||||
const name = p.loadNameFromRef(private.ref);
|
||||
const range = logger.Range{ .loc = value.loc, .len = @as(i32, @intCast(name.len)) };
|
||||
p.log.addRangeErrorFmt(p.source, range, p.allocator, "Deleting the private name \"{s}\" is forbidden", .{name}) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
return p.newExpr(E.Unary{ .op = .un_delete, .value = value }, loc);
|
||||
}
|
||||
fn t_plus(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == .t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return p.newExpr(E.Unary{ .op = .un_pos, .value = value }, loc);
|
||||
}
|
||||
fn t_minus(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == .t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return p.newExpr(E.Unary{ .op = .un_neg, .value = value }, loc);
|
||||
}
|
||||
fn t_tilde(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == .t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return p.newExpr(E.Unary{ .op = .un_cpl, .value = value }, loc);
|
||||
}
|
||||
fn t_exclamation(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const value = try p.parseExpr(.prefix);
|
||||
if (p.lexer.token == .t_asterisk_asterisk) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
return p.newExpr(E.Unary{ .op = .un_not, .value = value }, loc);
|
||||
}
|
||||
fn t_minus_minus(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
return p.newExpr(E.Unary{ .op = .un_pre_dec, .value = try p.parseExpr(.prefix) }, loc);
|
||||
}
|
||||
fn t_plus_plus(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
return p.newExpr(E.Unary{ .op = .un_pre_inc, .value = try p.parseExpr(.prefix) }, loc);
|
||||
}
|
||||
fn t_function(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
return try p.parseFnExpr(loc, false, logger.Range.None);
|
||||
}
|
||||
fn t_class(noalias p: *P) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
const classKeyword = p.lexer.range();
|
||||
// markSyntaxFEatuer class
|
||||
try p.lexer.next();
|
||||
var name: ?js_ast.LocRef = null;
|
||||
|
||||
_ = p.pushScopeForParsePass(.class_name, loc) catch unreachable;
|
||||
|
||||
// Parse an optional class name
|
||||
if (p.lexer.token == .t_identifier) {
|
||||
const name_text = p.lexer.identifier;
|
||||
if (!is_typescript_enabled or !strings.eqlComptime(name_text, "implements")) {
|
||||
if (p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name_text, "await")) {
|
||||
p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"await\" as an identifier here") catch unreachable;
|
||||
}
|
||||
|
||||
name = js_ast.LocRef{
|
||||
.loc = p.lexer.loc(),
|
||||
.ref = p.newSymbol(
|
||||
.other,
|
||||
name_text,
|
||||
) catch unreachable,
|
||||
};
|
||||
try p.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
// Even anonymous classes can have TypeScript type parameters
|
||||
if (is_typescript_enabled) {
|
||||
_ = try p.skipTypeScriptTypeParameters(.{ .allow_in_out_variance_annotations = true, .allow_const_modifier = true });
|
||||
}
|
||||
|
||||
const class = try p.parseClass(classKeyword, name, ParseClassOptions{});
|
||||
p.popScope();
|
||||
|
||||
return p.newExpr(class, loc);
|
||||
}
|
||||
fn t_new(noalias p: *P, flags: Expr.EFlags) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
// Special-case the weird "new.target" expression here
|
||||
if (p.lexer.token == .t_dot) {
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.token != .t_identifier or !strings.eqlComptime(p.lexer.raw(), "target")) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
const range = logger.Range{ .loc = loc, .len = p.lexer.range().end().start - loc.start };
|
||||
|
||||
try p.lexer.next();
|
||||
return p.newExpr(E.NewTarget{ .range = range }, loc);
|
||||
}
|
||||
|
||||
// This wil become the new expr
|
||||
var new = p.newExpr(E.New{
|
||||
.target = undefined,
|
||||
.args = undefined,
|
||||
.close_parens_loc = undefined,
|
||||
}, loc);
|
||||
|
||||
try p.parseExprWithFlags(.member, flags, &new.data.e_new.target);
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
// Skip over TypeScript type arguments here if there are any
|
||||
if (p.lexer.token == .t_less_than) {
|
||||
_ = p.trySkipTypeScriptTypeArgumentsWithBacktracking();
|
||||
}
|
||||
}
|
||||
|
||||
if (p.lexer.token == .t_open_paren) {
|
||||
const call_args = try p.parseCallArgs();
|
||||
new.data.e_new.args = call_args.list;
|
||||
new.data.e_new.close_parens_loc = call_args.loc;
|
||||
} else {
|
||||
new.data.e_new.close_parens_loc = .Empty;
|
||||
new.data.e_new.args = .{};
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
fn t_open_bracket(noalias p: *P, noalias errors: ?*DeferredErrors) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
var is_single_line = !p.lexer.has_newline_before;
|
||||
var items = ListManaged(Expr).init(p.allocator);
|
||||
var self_errors = DeferredErrors{};
|
||||
var comma_after_spread = logger.Loc{};
|
||||
|
||||
// Allow "in" inside arrays
|
||||
const old_allow_in = p.allow_in;
|
||||
p.allow_in = true;
|
||||
|
||||
while (p.lexer.token != .t_close_bracket) {
|
||||
switch (p.lexer.token) {
|
||||
.t_comma => {
|
||||
items.append(Expr{ .data = Prefill.Data.EMissing, .loc = p.lexer.loc() }) catch unreachable;
|
||||
},
|
||||
.t_dot_dot_dot => {
|
||||
if (errors != null)
|
||||
errors.?.array_spread_feature = p.lexer.range();
|
||||
|
||||
const dots_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
try items.ensureUnusedCapacity(1);
|
||||
const spread_expr: *Expr = &items.unusedCapacitySlice()[0];
|
||||
spread_expr.* = p.newExpr(E.Spread{ .value = undefined }, dots_loc);
|
||||
try p.parseExprOrBindings(.comma, &self_errors, &spread_expr.data.e_spread.value);
|
||||
items.items.len += 1;
|
||||
|
||||
// Commas are not allowed here when destructuring
|
||||
if (p.lexer.token == .t_comma) {
|
||||
comma_after_spread = p.lexer.loc();
|
||||
}
|
||||
},
|
||||
else => {
|
||||
try items.ensureUnusedCapacity(1);
|
||||
const item: *Expr = &items.unusedCapacitySlice()[0];
|
||||
try p.parseExprOrBindings(.comma, &self_errors, item);
|
||||
items.items.len += 1;
|
||||
},
|
||||
}
|
||||
|
||||
if (p.lexer.token != .t_comma) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
|
||||
const close_bracket_loc = p.lexer.loc();
|
||||
try p.lexer.expect(.t_close_bracket);
|
||||
p.allow_in = old_allow_in;
|
||||
|
||||
// Is this a binding pattern?
|
||||
if (p.willNeedBindingPattern()) {
|
||||
// noop
|
||||
} else if (errors == null) {
|
||||
// Is this an expression?
|
||||
p.logExprErrors(&self_errors);
|
||||
} else {
|
||||
// In this case, we can't distinguish between the two yet
|
||||
self_errors.mergeInto(errors.?);
|
||||
}
|
||||
return p.newExpr(E.Array{
|
||||
.items = ExprNodeList.fromList(items),
|
||||
.comma_after_spread = comma_after_spread.toNullable(),
|
||||
.is_single_line = is_single_line,
|
||||
.close_bracket_loc = close_bracket_loc,
|
||||
}, loc);
|
||||
}
|
||||
fn t_open_brace(noalias p: *P, noalias errors: ?*DeferredErrors) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
var is_single_line = !p.lexer.has_newline_before;
|
||||
var properties = ListManaged(G.Property).init(p.allocator);
|
||||
var self_errors = DeferredErrors{};
|
||||
var comma_after_spread: logger.Loc = logger.Loc{};
|
||||
|
||||
// Allow "in" inside object literals
|
||||
const old_allow_in = p.allow_in;
|
||||
p.allow_in = true;
|
||||
|
||||
while (p.lexer.token != .t_close_brace) {
|
||||
if (p.lexer.token == .t_dot_dot_dot) {
|
||||
try p.lexer.next();
|
||||
try properties.ensureUnusedCapacity(1);
|
||||
const property: *G.Property = &properties.unusedCapacitySlice()[0];
|
||||
property.* = .{
|
||||
.kind = .spread,
|
||||
.value = Expr.empty,
|
||||
};
|
||||
|
||||
try p.parseExprOrBindings(
|
||||
.comma,
|
||||
&self_errors,
|
||||
&(property.value.?),
|
||||
);
|
||||
properties.items.len += 1;
|
||||
|
||||
// Commas are not allowed here when destructuring
|
||||
if (p.lexer.token == .t_comma) {
|
||||
comma_after_spread = p.lexer.loc();
|
||||
}
|
||||
} else {
|
||||
// This property may turn out to be a type in TypeScript, which should be ignored
|
||||
var propertyOpts = PropertyOpts{};
|
||||
if (try p.parseProperty(.normal, &propertyOpts, &self_errors)) |prop| {
|
||||
if (comptime Environment.allow_assert) {
|
||||
assert(prop.key != null or prop.value != null);
|
||||
}
|
||||
properties.append(prop) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
if (p.lexer.token != .t_comma) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (p.lexer.has_newline_before) {
|
||||
is_single_line = false;
|
||||
}
|
||||
|
||||
const close_brace_loc = p.lexer.loc();
|
||||
try p.lexer.expect(.t_close_brace);
|
||||
p.allow_in = old_allow_in;
|
||||
|
||||
if (p.willNeedBindingPattern()) {
|
||||
// Is this a binding pattern?
|
||||
} else if (errors == null) {
|
||||
// Is this an expression?
|
||||
p.logExprErrors(&self_errors);
|
||||
} else {
|
||||
// In this case, we can't distinguish between the two yet
|
||||
self_errors.mergeInto(errors.?);
|
||||
}
|
||||
|
||||
return p.newExpr(E.Object{
|
||||
.properties = G.Property.List.fromList(properties),
|
||||
.comma_after_spread = if (comma_after_spread.start > 0)
|
||||
comma_after_spread
|
||||
else
|
||||
null,
|
||||
.is_single_line = is_single_line,
|
||||
.close_brace_loc = close_brace_loc,
|
||||
}, loc);
|
||||
}
|
||||
fn t_less_than(noalias p: *P, level: Level, noalias errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
// This is a very complicated and highly ambiguous area of TypeScript
|
||||
// syntax. Many similar-looking things are overloaded.
|
||||
//
|
||||
// TS:
|
||||
//
|
||||
// A type cast:
|
||||
// <A>(x)
|
||||
// <[]>(x)
|
||||
// <A[]>(x)
|
||||
//
|
||||
// An arrow function with type parameters:
|
||||
// <A>(x) => {}
|
||||
// <A, B>(x) => {}
|
||||
// <A = B>(x) => {}
|
||||
// <A extends B>(x) => {}
|
||||
//
|
||||
// TSX:
|
||||
//
|
||||
// A JSX element:
|
||||
// <A>(x) => {}</A>
|
||||
// <A extends>(x) => {}</A>
|
||||
// <A extends={false}>(x) => {}</A>
|
||||
//
|
||||
// An arrow function with type parameters:
|
||||
// <A, B>(x) => {}
|
||||
// <A extends B>(x) => {}
|
||||
//
|
||||
// A syntax error:
|
||||
// <[]>(x)
|
||||
// <A[]>(x)
|
||||
// <A>(x) => {}
|
||||
// <A = B>(x) => {}
|
||||
if (comptime is_typescript_enabled and is_jsx_enabled) {
|
||||
if (try TypeScript.isTSArrowFnJSX(p)) {
|
||||
_ = try p.skipTypeScriptTypeParameters(TypeParameterFlag{
|
||||
.allow_const_modifier = true,
|
||||
});
|
||||
try p.lexer.expect(.t_open_paren);
|
||||
return try p.parseParenExpr(loc, level, ParenExprOpts{ .force_arrow_fn = true });
|
||||
}
|
||||
}
|
||||
|
||||
if (is_jsx_enabled) {
|
||||
// Use NextInsideJSXElement() instead of Next() so we parse "<<" as "<"
|
||||
try p.lexer.nextInsideJSXElement();
|
||||
const element = try p.parseJSXElement(loc);
|
||||
|
||||
// The call to parseJSXElement() above doesn't consume the last
|
||||
// TGreaterThan because the caller knows what Next() function to call.
|
||||
// Use Next() instead of NextInsideJSXElement() here since the next
|
||||
// token is an expression.
|
||||
try p.lexer.next();
|
||||
return element;
|
||||
}
|
||||
|
||||
if (is_typescript_enabled) {
|
||||
// This is either an old-style type cast or a generic lambda function
|
||||
|
||||
// "<T>(x)"
|
||||
// "<T>(x) => {}"
|
||||
switch (p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking()) {
|
||||
.did_not_skip_anything => {},
|
||||
else => |result| {
|
||||
try p.lexer.expect(.t_open_paren);
|
||||
return p.parseParenExpr(loc, level, ParenExprOpts{
|
||||
.force_arrow_fn = result == .definitely_type_parameters,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// "<T>x"
|
||||
try p.lexer.next();
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
try p.lexer.expectGreaterThan(false);
|
||||
return p.parsePrefix(level, errors, flags);
|
||||
}
|
||||
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
fn t_import(noalias p: *P, level: Level) anyerror!Expr {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
return p.parseImportExpr(loc, level);
|
||||
}
|
||||
|
||||
// Before splitting this up, this used 3 KB of stack space per call.
|
||||
pub fn parsePrefix(noalias p: *P, level: Level, noalias errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr {
|
||||
return switch (p.lexer.token) {
|
||||
.t_open_bracket => t_open_bracket(p, errors),
|
||||
.t_open_brace => t_open_brace(p, errors),
|
||||
.t_less_than => t_less_than(p, level, errors, flags),
|
||||
.t_import => t_import(p, level),
|
||||
.t_open_paren => t_open_paren(p, level),
|
||||
.t_private_identifier => t_private_identifier(p, level),
|
||||
.t_identifier => t_identifier(p, level),
|
||||
.t_false => t_false(p),
|
||||
.t_true => t_true(p),
|
||||
.t_null => t_null(p),
|
||||
.t_this => t_this(p),
|
||||
.t_template_head => t_template_head(p),
|
||||
.t_numeric_literal => t_numeric_literal(p),
|
||||
.t_big_integer_literal => t_big_integer_literal(p),
|
||||
.t_string_literal, .t_no_substitution_template_literal => p.parseStringLiteral(),
|
||||
.t_slash_equals, .t_slash => t_slash(p),
|
||||
.t_void => t_void(p),
|
||||
.t_typeof => t_typeof(p),
|
||||
.t_delete => t_delete(p),
|
||||
.t_plus => t_plus(p),
|
||||
.t_minus => t_minus(p),
|
||||
.t_tilde => t_tilde(p),
|
||||
.t_exclamation => t_exclamation(p),
|
||||
.t_minus_minus => t_minus_minus(p),
|
||||
.t_plus_plus => t_plus_plus(p),
|
||||
.t_function => t_function(p),
|
||||
.t_class => t_class(p),
|
||||
.t_new => t_new(p, flags),
|
||||
.t_super => t_super(p, level),
|
||||
else => {
|
||||
@branchHint(.cold);
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const assert = bun.assert;
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const B = js_ast.B;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const LocRef = js_ast.LocRef;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Arg = G.Arg;
|
||||
const Property = G.Property;
|
||||
|
||||
const Op = js_ast.Op;
|
||||
const Level = js_ast.Op.Level;
|
||||
|
||||
const js_lexer = bun.js_lexer;
|
||||
const T = js_lexer.T;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const AsyncPrefixExpression = js_parser.AsyncPrefixExpression;
|
||||
const DeferredErrors = js_parser.DeferredErrors;
|
||||
const FnOrArrowDataParse = js_parser.FnOrArrowDataParse;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const ParenExprOpts = js_parser.ParenExprOpts;
|
||||
const ParseClassOptions = js_parser.ParseClassOptions;
|
||||
const Prefill = js_parser.Prefill;
|
||||
const PropertyOpts = js_parser.PropertyOpts;
|
||||
const TypeParameterFlag = js_parser.TypeParameterFlag;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
const ListManaged = std.ArrayList;
|
||||
@@ -1,575 +0,0 @@
|
||||
pub fn ParseProperty(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
const is_typescript_enabled = P.is_typescript_enabled;
|
||||
|
||||
fn parseMethodExpression(p: *P, kind: Property.Kind, opts: *PropertyOpts, is_computed: bool, key: *Expr, key_range: logger.Range) anyerror!?G.Property {
|
||||
if (p.lexer.token == .t_open_paren and kind != .get and kind != .set) {
|
||||
// markSyntaxFeature object extensions
|
||||
}
|
||||
|
||||
const loc = p.lexer.loc();
|
||||
const scope_index = p.pushScopeForParsePass(.function_args, loc) catch unreachable;
|
||||
var is_constructor = false;
|
||||
|
||||
// Forbid the names "constructor" and "prototype" in some cases
|
||||
if (opts.is_class and !is_computed) {
|
||||
switch (key.data) {
|
||||
.e_string => |str| {
|
||||
if (!opts.is_static and str.eqlComptime("constructor")) {
|
||||
if (kind == .get) {
|
||||
p.log.addRangeError(p.source, key_range, "Class constructor cannot be a getter") catch unreachable;
|
||||
} else if (kind == .set) {
|
||||
p.log.addRangeError(p.source, key_range, "Class constructor cannot be a setter") catch unreachable;
|
||||
} else if (opts.is_async) {
|
||||
p.log.addRangeError(p.source, key_range, "Class constructor cannot be an async function") catch unreachable;
|
||||
} else if (opts.is_generator) {
|
||||
p.log.addRangeError(p.source, key_range, "Class constructor cannot be a generator function") catch unreachable;
|
||||
} else {
|
||||
is_constructor = true;
|
||||
}
|
||||
} else if (opts.is_static and str.eqlComptime("prototype")) {
|
||||
p.log.addRangeError(p.source, key_range, "Invalid static method name \"prototype\"") catch unreachable;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
var func = try p.parseFn(null, FnOrArrowDataParse{
|
||||
.async_range = opts.async_range,
|
||||
.needs_async_loc = key.loc,
|
||||
.has_async_range = !opts.async_range.isEmpty(),
|
||||
.allow_await = if (opts.is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident,
|
||||
.allow_yield = if (opts.is_generator) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident,
|
||||
.allow_super_call = opts.class_has_extends and is_constructor,
|
||||
.allow_super_property = true,
|
||||
.allow_ts_decorators = opts.allow_ts_decorators,
|
||||
.is_constructor = is_constructor,
|
||||
.has_decorators = opts.ts_decorators.len > 0 or (opts.has_class_decorators and is_constructor),
|
||||
|
||||
// Only allow omitting the body if we're parsing TypeScript class
|
||||
.allow_missing_body_for_type_script = is_typescript_enabled and opts.is_class,
|
||||
});
|
||||
|
||||
opts.has_argument_decorators = opts.has_argument_decorators or p.fn_or_arrow_data_parse.has_argument_decorators;
|
||||
p.fn_or_arrow_data_parse.has_argument_decorators = false;
|
||||
|
||||
// "class Foo { foo(): void; foo(): void {} }"
|
||||
if (func.flags.contains(.is_forward_declaration)) {
|
||||
// Skip this property entirely
|
||||
p.popAndDiscardScope(scope_index);
|
||||
return null;
|
||||
}
|
||||
|
||||
p.popScope();
|
||||
func.flags.insert(.is_unique_formal_parameters);
|
||||
const value = p.newExpr(E.Function{ .func = func }, loc);
|
||||
|
||||
// Enforce argument rules for accessors
|
||||
switch (kind) {
|
||||
.get => {
|
||||
if (func.args.len > 0) {
|
||||
const r = js_lexer.rangeOfIdentifier(p.source, func.args[0].binding.loc);
|
||||
p.log.addRangeErrorFmt(p.source, r, p.allocator, "Getter {s} must have zero arguments", .{p.keyNameForError(key)}) catch unreachable;
|
||||
}
|
||||
},
|
||||
.set => {
|
||||
if (func.args.len != 1) {
|
||||
var r = js_lexer.rangeOfIdentifier(p.source, if (func.args.len > 0) func.args[0].binding.loc else loc);
|
||||
if (func.args.len > 1) {
|
||||
r = js_lexer.rangeOfIdentifier(p.source, func.args[1].binding.loc);
|
||||
}
|
||||
p.log.addRangeErrorFmt(p.source, r, p.allocator, "Setter {s} must have exactly 1 argument (there are {d})", .{ p.keyNameForError(key), func.args.len }) catch unreachable;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
// Special-case private identifiers
|
||||
switch (key.data) {
|
||||
.e_private_identifier => |*private| {
|
||||
const declare: Symbol.Kind = switch (kind) {
|
||||
.get => if (opts.is_static)
|
||||
.private_static_get
|
||||
else
|
||||
.private_get,
|
||||
|
||||
.set => if (opts.is_static)
|
||||
.private_static_set
|
||||
else
|
||||
.private_set,
|
||||
else => if (opts.is_static)
|
||||
.private_static_method
|
||||
else
|
||||
.private_method,
|
||||
};
|
||||
|
||||
const name = p.loadNameFromRef(private.ref);
|
||||
if (strings.eqlComptime(name, "#constructor")) {
|
||||
p.log.addRangeError(p.source, key_range, "Invalid method name \"#constructor\"") catch unreachable;
|
||||
}
|
||||
private.ref = p.declareSymbol(declare, key.loc, name) catch unreachable;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return G.Property{
|
||||
.ts_decorators = ExprNodeList.init(opts.ts_decorators),
|
||||
.kind = kind,
|
||||
.flags = Flags.Property.init(.{
|
||||
.is_computed = is_computed,
|
||||
.is_method = true,
|
||||
.is_static = opts.is_static,
|
||||
}),
|
||||
.key = key.*,
|
||||
.value = value,
|
||||
.ts_metadata = .m_function,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parseProperty(p: *P, kind_: Property.Kind, opts: *PropertyOpts, errors_: ?*DeferredErrors) anyerror!?G.Property {
|
||||
var kind = kind_;
|
||||
var errors = errors_;
|
||||
// This while loop exists to conserve stack space by reducing (but not completely eliminating) recursion.
|
||||
restart: while (true) {
|
||||
var key: Expr = Expr{ .loc = logger.Loc.Empty, .data = .{ .e_missing = E.Missing{} } };
|
||||
const key_range = p.lexer.range();
|
||||
var is_computed = false;
|
||||
|
||||
switch (p.lexer.token) {
|
||||
.t_numeric_literal => {
|
||||
key = p.newExpr(E.Number{
|
||||
.value = p.lexer.number,
|
||||
}, p.lexer.loc());
|
||||
// p.checkForLegacyOctalLiteral()
|
||||
try p.lexer.next();
|
||||
},
|
||||
.t_string_literal => {
|
||||
key = try p.parseStringLiteral();
|
||||
},
|
||||
.t_big_integer_literal => {
|
||||
key = p.newExpr(E.BigInt{ .value = p.lexer.identifier }, p.lexer.loc());
|
||||
// markSyntaxFeature
|
||||
try p.lexer.next();
|
||||
},
|
||||
.t_private_identifier => {
|
||||
if (!opts.is_class or opts.ts_decorators.len > 0) {
|
||||
try p.lexer.expected(.t_identifier);
|
||||
}
|
||||
|
||||
key = p.newExpr(E.PrivateIdentifier{ .ref = p.storeNameInRef(p.lexer.identifier) catch unreachable }, p.lexer.loc());
|
||||
try p.lexer.next();
|
||||
},
|
||||
.t_open_bracket => {
|
||||
is_computed = true;
|
||||
// p.markSyntaxFeature(compat.objectExtensions, p.lexer.range())
|
||||
try p.lexer.next();
|
||||
const wasIdentifier = p.lexer.token == .t_identifier;
|
||||
const expr = try p.parseExpr(.comma);
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
|
||||
// Handle index signatures
|
||||
if (p.lexer.token == .t_colon and wasIdentifier and opts.is_class) {
|
||||
switch (expr.data) {
|
||||
.e_identifier => {
|
||||
try p.lexer.next();
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
try p.lexer.expect(.t_close_bracket);
|
||||
try p.lexer.expect(.t_colon);
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
try p.lexer.expectOrInsertSemicolon();
|
||||
|
||||
// Skip this property entirely
|
||||
return null;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_close_bracket);
|
||||
key = expr;
|
||||
},
|
||||
.t_asterisk => {
|
||||
if (kind != .normal or opts.is_generator) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
opts.is_generator = true;
|
||||
kind = .normal;
|
||||
continue :restart;
|
||||
},
|
||||
|
||||
else => {
|
||||
const name = p.lexer.identifier;
|
||||
const raw = p.lexer.raw();
|
||||
const name_range = p.lexer.range();
|
||||
|
||||
if (!p.lexer.isIdentifierOrKeyword()) {
|
||||
try p.lexer.expect(.t_identifier);
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
// Support contextual keywords
|
||||
if (kind == .normal and !opts.is_generator) {
|
||||
// Does the following token look like a key?
|
||||
const couldBeModifierKeyword = p.lexer.isIdentifierOrKeyword() or switch (p.lexer.token) {
|
||||
.t_open_bracket, .t_numeric_literal, .t_string_literal, .t_asterisk, .t_private_identifier => true,
|
||||
else => false,
|
||||
};
|
||||
|
||||
// If so, check for a modifier keyword
|
||||
if (couldBeModifierKeyword) {
|
||||
// TODO: micro-optimization, use a smaller list for non-typescript files.
|
||||
if (js_lexer.PropertyModifierKeyword.List.get(name)) |keyword| {
|
||||
switch (keyword) {
|
||||
.p_get => {
|
||||
if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_get) {
|
||||
kind = .get;
|
||||
errors = null;
|
||||
continue :restart;
|
||||
}
|
||||
},
|
||||
|
||||
.p_set => {
|
||||
if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_set) {
|
||||
// p.markSyntaxFeature(ObjectAccessors, name_range)
|
||||
kind = .set;
|
||||
errors = null;
|
||||
continue :restart;
|
||||
}
|
||||
},
|
||||
.p_async => {
|
||||
if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_async and !p.lexer.has_newline_before) {
|
||||
opts.is_async = true;
|
||||
opts.async_range = name_range;
|
||||
|
||||
// p.markSyntaxFeature(ObjectAccessors, name_range)
|
||||
|
||||
errors = null;
|
||||
continue :restart;
|
||||
}
|
||||
},
|
||||
.p_static => {
|
||||
if (!opts.is_static and !opts.is_async and opts.is_class and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_get) == .p_static) {
|
||||
opts.is_static = true;
|
||||
kind = .normal;
|
||||
errors = null;
|
||||
continue :restart;
|
||||
}
|
||||
},
|
||||
.p_declare => {
|
||||
// skip declare keyword entirely
|
||||
// https://github.com/oven-sh/bun/issues/1907
|
||||
if (opts.is_class and is_typescript_enabled and strings.eqlComptime(raw, "declare")) {
|
||||
const scope_index = p.scopes_in_order.items.len;
|
||||
if (try p.parseProperty(kind, opts, null)) |_prop| {
|
||||
var prop = _prop;
|
||||
if (prop.kind == .normal and prop.value == null and opts.ts_decorators.len > 0) {
|
||||
prop.kind = .declare;
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
p.discardScopesUpTo(scope_index);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.p_abstract => {
|
||||
if (opts.is_class and is_typescript_enabled and !opts.is_ts_abstract and strings.eqlComptime(raw, "abstract")) {
|
||||
opts.is_ts_abstract = true;
|
||||
const scope_index = p.scopes_in_order.items.len;
|
||||
if (try p.parseProperty(kind, opts, null)) |*prop| {
|
||||
if (prop.kind == .normal and prop.value == null and opts.ts_decorators.len > 0) {
|
||||
var prop_ = prop.*;
|
||||
prop_.kind = .abstract;
|
||||
return prop_;
|
||||
}
|
||||
}
|
||||
p.discardScopesUpTo(scope_index);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.p_private, .p_protected, .p_public, .p_readonly, .p_override => {
|
||||
// Skip over TypeScript keywords
|
||||
if (opts.is_class and is_typescript_enabled and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == keyword) {
|
||||
errors = null;
|
||||
continue :restart;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if (p.lexer.token == .t_open_brace and strings.eqlComptime(name, "static")) {
|
||||
const loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
const old_fn_or_arrow_data_parse = p.fn_or_arrow_data_parse;
|
||||
p.fn_or_arrow_data_parse = .{
|
||||
.is_return_disallowed = true,
|
||||
.allow_super_property = true,
|
||||
.allow_await = .forbid_all,
|
||||
};
|
||||
|
||||
_ = try p.pushScopeForParsePass(.class_static_init, loc);
|
||||
var _parse_opts = ParseStatementOptions{};
|
||||
const stmts = try p.parseStmtsUpTo(.t_close_brace, &_parse_opts);
|
||||
|
||||
p.popScope();
|
||||
|
||||
p.fn_or_arrow_data_parse = old_fn_or_arrow_data_parse;
|
||||
try p.lexer.expect(.t_close_brace);
|
||||
|
||||
const block = p.allocator.create(
|
||||
G.ClassStaticBlock,
|
||||
) catch unreachable;
|
||||
|
||||
block.* = G.ClassStaticBlock{
|
||||
.stmts = js_ast.BabyList(Stmt).init(stmts),
|
||||
.loc = loc,
|
||||
};
|
||||
|
||||
return G.Property{
|
||||
.kind = .class_static_block,
|
||||
.class_static_block = block,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle invalid identifiers in property names
|
||||
// https://github.com/oven-sh/bun/issues/12039
|
||||
if (p.lexer.token == .t_syntax_error) {
|
||||
p.log.addRangeErrorFmt(p.source, name_range, p.allocator, "Unexpected {}", .{bun.fmt.quote(name)}) catch bun.outOfMemory();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
key = p.newExpr(E.String{ .data = name }, name_range.loc);
|
||||
|
||||
// Parse a shorthand property
|
||||
const isShorthandProperty = !opts.is_class and
|
||||
kind == .normal and
|
||||
p.lexer.token != .t_colon and
|
||||
p.lexer.token != .t_open_paren and
|
||||
p.lexer.token != .t_less_than and
|
||||
!opts.is_generator and
|
||||
!opts.is_async and
|
||||
!js_lexer.Keywords.has(name);
|
||||
|
||||
if (isShorthandProperty) {
|
||||
if ((p.fn_or_arrow_data_parse.allow_await != .allow_ident and
|
||||
strings.eqlComptime(name, "await")) or
|
||||
(p.fn_or_arrow_data_parse.allow_yield != .allow_ident and
|
||||
strings.eqlComptime(name, "yield")))
|
||||
{
|
||||
if (strings.eqlComptime(name, "await")) {
|
||||
p.log.addRangeError(p.source, name_range, "Cannot use \"await\" here") catch unreachable;
|
||||
} else {
|
||||
p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" here") catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
const ref = p.storeNameInRef(name) catch unreachable;
|
||||
const value = p.newExpr(E.Identifier{ .ref = ref }, key.loc);
|
||||
|
||||
// Destructuring patterns have an optional default value
|
||||
var initializer: ?Expr = null;
|
||||
if (errors != null and p.lexer.token == .t_equals) {
|
||||
errors.?.invalid_expr_default_value = p.lexer.range();
|
||||
try p.lexer.next();
|
||||
initializer = try p.parseExpr(.comma);
|
||||
}
|
||||
|
||||
return G.Property{
|
||||
.kind = kind,
|
||||
.key = key,
|
||||
.value = value,
|
||||
.initializer = initializer,
|
||||
.flags = Flags.Property.init(.{
|
||||
.was_shorthand = true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var has_type_parameters = false;
|
||||
var has_definite_assignment_assertion_operator = false;
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
if (opts.is_class) {
|
||||
if (p.lexer.token == .t_question) {
|
||||
// "class X { foo?: number }"
|
||||
// "class X { foo!: number }"
|
||||
try p.lexer.next();
|
||||
} else if (p.lexer.token == .t_exclamation and
|
||||
!p.lexer.has_newline_before and
|
||||
kind == .normal and
|
||||
!opts.is_async and
|
||||
!opts.is_generator)
|
||||
{
|
||||
// "class X { foo!: number }"
|
||||
try p.lexer.next();
|
||||
has_definite_assignment_assertion_operator = true;
|
||||
}
|
||||
}
|
||||
|
||||
// "class X { foo?<T>(): T }"
|
||||
// "const x = { foo<T>(): T {} }"
|
||||
if (!has_definite_assignment_assertion_operator) {
|
||||
has_type_parameters = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true }) != .did_not_skip_anything;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a class field with an optional initial value
|
||||
if (opts.is_class and
|
||||
kind == .normal and !opts.is_async and
|
||||
!opts.is_generator and
|
||||
p.lexer.token != .t_open_paren and
|
||||
!has_type_parameters and
|
||||
(p.lexer.token != .t_open_paren or has_definite_assignment_assertion_operator))
|
||||
{
|
||||
var initializer: ?Expr = null;
|
||||
var ts_metadata = TypeScript.Metadata.default;
|
||||
|
||||
// Forbid the names "constructor" and "prototype" in some cases
|
||||
if (!is_computed) {
|
||||
switch (key.data) {
|
||||
.e_string => |str| {
|
||||
if (str.eqlComptime("constructor") or (opts.is_static and str.eqlComptime("prototype"))) {
|
||||
// TODO: fmt error message to include string value.
|
||||
p.log.addRangeError(p.source, key_range, "Invalid field name") catch unreachable;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
// Skip over types
|
||||
if (p.lexer.token == .t_colon) {
|
||||
try p.lexer.next();
|
||||
if (p.options.features.emit_decorator_metadata and opts.is_class and opts.ts_decorators.len > 0) {
|
||||
ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest);
|
||||
} else {
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p.lexer.token == .t_equals) {
|
||||
if (comptime is_typescript_enabled) {
|
||||
if (!opts.declare_range.isEmpty()) {
|
||||
try p.log.addRangeError(p.source, p.lexer.range(), "Class fields that use \"declare\" cannot be initialized");
|
||||
}
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
// "this" and "super" property access is allowed in field initializers
|
||||
const old_is_this_disallowed = p.fn_or_arrow_data_parse.is_this_disallowed;
|
||||
const old_allow_super_property = p.fn_or_arrow_data_parse.allow_super_property;
|
||||
p.fn_or_arrow_data_parse.is_this_disallowed = false;
|
||||
p.fn_or_arrow_data_parse.allow_super_property = true;
|
||||
|
||||
initializer = try p.parseExpr(.comma);
|
||||
|
||||
p.fn_or_arrow_data_parse.is_this_disallowed = old_is_this_disallowed;
|
||||
p.fn_or_arrow_data_parse.allow_super_property = old_allow_super_property;
|
||||
}
|
||||
|
||||
// Special-case private identifiers
|
||||
switch (key.data) {
|
||||
.e_private_identifier => |*private| {
|
||||
const name = p.loadNameFromRef(private.ref);
|
||||
if (strings.eqlComptime(name, "#constructor")) {
|
||||
p.log.addRangeError(p.source, key_range, "Invalid field name \"#constructor\"") catch unreachable;
|
||||
}
|
||||
|
||||
const declare: js_ast.Symbol.Kind = if (opts.is_static)
|
||||
.private_static_field
|
||||
else
|
||||
.private_field;
|
||||
|
||||
private.ref = p.declareSymbol(declare, key.loc, name) catch unreachable;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
try p.lexer.expectOrInsertSemicolon();
|
||||
|
||||
return G.Property{
|
||||
.ts_decorators = ExprNodeList.init(opts.ts_decorators),
|
||||
.kind = kind,
|
||||
.flags = Flags.Property.init(.{
|
||||
.is_computed = is_computed,
|
||||
.is_static = opts.is_static,
|
||||
}),
|
||||
.key = key,
|
||||
.initializer = initializer,
|
||||
.ts_metadata = ts_metadata,
|
||||
};
|
||||
}
|
||||
|
||||
// Parse a method expression
|
||||
if (p.lexer.token == .t_open_paren or kind != .normal or opts.is_class or opts.is_async or opts.is_generator) {
|
||||
return parseMethodExpression(p, kind, opts, is_computed, &key, key_range);
|
||||
}
|
||||
|
||||
// Parse an object key/value pair
|
||||
try p.lexer.expect(.t_colon);
|
||||
var property: G.Property = .{
|
||||
.kind = kind,
|
||||
.flags = Flags.Property.init(.{
|
||||
.is_computed = is_computed,
|
||||
}),
|
||||
.key = key,
|
||||
.value = Expr{ .data = .e_missing, .loc = .{} },
|
||||
};
|
||||
|
||||
try p.parseExprOrBindings(.comma, errors, &property.value.?);
|
||||
return property;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const Flags = js_ast.Flags;
|
||||
const Stmt = js_ast.Stmt;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Property = G.Property;
|
||||
|
||||
const js_lexer = bun.js_lexer;
|
||||
const T = js_lexer.T;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const AwaitOrYield = js_parser.AwaitOrYield;
|
||||
const DeferredErrors = js_parser.DeferredErrors;
|
||||
const FnOrArrowDataParse = js_parser.FnOrArrowDataParse;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const ParseStatementOptions = js_parser.ParseStatementOptions;
|
||||
const PropertyOpts = js_parser.PropertyOpts;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
const options = js_parser.options;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,956 +0,0 @@
|
||||
pub fn ParseSuffix(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
const is_typescript_enabled = P.is_typescript_enabled;
|
||||
|
||||
fn handleTypescriptAs(p: *P, level: Level) anyerror!Continuation {
|
||||
if (is_typescript_enabled and level.lt(.compare) and !p.lexer.has_newline_before and (p.lexer.isContextualKeyword("as") or p.lexer.isContextualKeyword("satisfies"))) {
|
||||
try p.lexer.next();
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
|
||||
// These tokens are not allowed to follow a cast expression. This isn't
|
||||
// an outright error because it may be on a new line, in which case it's
|
||||
// the start of a new expression when it's after a cast:
|
||||
//
|
||||
// x = y as z
|
||||
// (something);
|
||||
//
|
||||
switch (p.lexer.token) {
|
||||
.t_plus_plus,
|
||||
.t_minus_minus,
|
||||
.t_no_substitution_template_literal,
|
||||
.t_template_head,
|
||||
.t_open_paren,
|
||||
.t_open_bracket,
|
||||
.t_question_dot,
|
||||
=> {
|
||||
p.forbid_suffix_after_as_loc = p.lexer.loc();
|
||||
return .done;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (p.lexer.token.isAssign()) {
|
||||
p.forbid_suffix_after_as_loc = p.lexer.loc();
|
||||
return .done;
|
||||
}
|
||||
return .next;
|
||||
}
|
||||
return .done;
|
||||
}
|
||||
|
||||
fn t_dot(p: *P, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation {
|
||||
try p.lexer.next();
|
||||
const target = left.*;
|
||||
|
||||
if (p.lexer.token == .t_private_identifier and p.allow_private_identifiers) {
|
||||
// "a.#b"
|
||||
// "a?.b.#c"
|
||||
switch (left.data) {
|
||||
.e_super => {
|
||||
try p.lexer.expected(.t_identifier);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
const name = p.lexer.identifier;
|
||||
const name_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const ref = p.storeNameInRef(name) catch unreachable;
|
||||
left.* = p.newExpr(E.Index{
|
||||
.target = target,
|
||||
.index = p.newExpr(
|
||||
E.PrivateIdentifier{
|
||||
.ref = ref,
|
||||
},
|
||||
name_loc,
|
||||
),
|
||||
.optional_chain = old_optional_chain,
|
||||
}, left.loc);
|
||||
} else {
|
||||
// "a.b"
|
||||
// "a?.b.c"
|
||||
if (!p.lexer.isIdentifierOrKeyword()) {
|
||||
try p.lexer.expect(.t_identifier);
|
||||
}
|
||||
|
||||
const name = p.lexer.identifier;
|
||||
const name_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
left.* = p.newExpr(
|
||||
E.Dot{
|
||||
.target = target,
|
||||
.name = name,
|
||||
.name_loc = name_loc,
|
||||
.optional_chain = old_optional_chain,
|
||||
},
|
||||
left.loc,
|
||||
);
|
||||
}
|
||||
optional_chain.* = old_optional_chain;
|
||||
return .next;
|
||||
}
|
||||
fn t_question_dot(p: *P, level: Level, optional_chain: *?OptionalChain, left: *Expr) anyerror!Continuation {
|
||||
try p.lexer.next();
|
||||
var optional_start: ?OptionalChain = OptionalChain.start;
|
||||
|
||||
// Remove unnecessary optional chains
|
||||
if (p.options.features.minify_syntax) {
|
||||
const result = SideEffects.toNullOrUndefined(p, left.data);
|
||||
if (result.ok and !result.value) {
|
||||
optional_start = null;
|
||||
}
|
||||
}
|
||||
|
||||
switch (p.lexer.token) {
|
||||
.t_open_bracket => {
|
||||
// "a?.[b]"
|
||||
try p.lexer.next();
|
||||
|
||||
// allow "in" inside the brackets;
|
||||
const old_allow_in = p.allow_in;
|
||||
p.allow_in = true;
|
||||
|
||||
const index = try p.parseExpr(.lowest);
|
||||
|
||||
p.allow_in = old_allow_in;
|
||||
|
||||
try p.lexer.expect(.t_close_bracket);
|
||||
left.* = p.newExpr(
|
||||
E.Index{ .target = left.*, .index = index, .optional_chain = optional_start },
|
||||
left.loc,
|
||||
);
|
||||
},
|
||||
|
||||
.t_open_paren => {
|
||||
// "a?.()"
|
||||
if (level.gte(.call)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
const list_loc = try p.parseCallArgs();
|
||||
left.* = p.newExpr(E.Call{
|
||||
.target = left.*,
|
||||
.args = list_loc.list,
|
||||
.close_paren_loc = list_loc.loc,
|
||||
.optional_chain = optional_start,
|
||||
}, left.loc);
|
||||
},
|
||||
.t_less_than, .t_less_than_less_than => {
|
||||
// "a?.<T>()"
|
||||
if (comptime !is_typescript_enabled) {
|
||||
try p.lexer.expected(.t_identifier);
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
_ = try p.skipTypeScriptTypeArguments(false);
|
||||
if (p.lexer.token != .t_open_paren) {
|
||||
try p.lexer.expected(.t_open_paren);
|
||||
}
|
||||
|
||||
if (level.gte(.call)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
const list_loc = try p.parseCallArgs();
|
||||
left.* = p.newExpr(E.Call{
|
||||
.target = left.*,
|
||||
.args = list_loc.list,
|
||||
.close_paren_loc = list_loc.loc,
|
||||
.optional_chain = optional_start,
|
||||
}, left.loc);
|
||||
},
|
||||
else => {
|
||||
if (p.lexer.token == .t_private_identifier and p.allow_private_identifiers) {
|
||||
// "a?.#b"
|
||||
const name = p.lexer.identifier;
|
||||
const name_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
const ref = p.storeNameInRef(name) catch unreachable;
|
||||
left.* = p.newExpr(E.Index{
|
||||
.target = left.*,
|
||||
.index = p.newExpr(
|
||||
E.PrivateIdentifier{
|
||||
.ref = ref,
|
||||
},
|
||||
name_loc,
|
||||
),
|
||||
.optional_chain = optional_start,
|
||||
}, left.loc);
|
||||
} else {
|
||||
// "a?.b"
|
||||
if (!p.lexer.isIdentifierOrKeyword()) {
|
||||
try p.lexer.expect(.t_identifier);
|
||||
}
|
||||
const name = p.lexer.identifier;
|
||||
const name_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
left.* = p.newExpr(E.Dot{
|
||||
.target = left.*,
|
||||
.name = name,
|
||||
.name_loc = name_loc,
|
||||
.optional_chain = optional_start,
|
||||
}, left.loc);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Only continue if we have started
|
||||
if ((optional_start orelse .continuation) == .start) {
|
||||
optional_chain.* = .continuation;
|
||||
}
|
||||
|
||||
return .next;
|
||||
}
|
||||
fn t_no_substitution_template_literal(p: *P, _: Level, _: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation {
|
||||
if (old_optional_chain != null) {
|
||||
p.log.addRangeError(p.source, p.lexer.range(), "Template literals cannot have an optional chain as a tag") catch unreachable;
|
||||
}
|
||||
// p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range());
|
||||
const head = p.lexer.rawTemplateContents();
|
||||
try p.lexer.next();
|
||||
|
||||
left.* = p.newExpr(E.Template{
|
||||
.tag = left.*,
|
||||
.head = .{ .raw = head },
|
||||
}, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_template_head(p: *P, _: Level, _: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation {
|
||||
if (old_optional_chain != null) {
|
||||
p.log.addRangeError(p.source, p.lexer.range(), "Template literals cannot have an optional chain as a tag") catch unreachable;
|
||||
}
|
||||
// p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range());
|
||||
const head = p.lexer.rawTemplateContents();
|
||||
const partsGroup = try p.parseTemplateParts(true);
|
||||
const tag = left.*;
|
||||
left.* = p.newExpr(E.Template{
|
||||
.tag = tag,
|
||||
.head = .{ .raw = head },
|
||||
.parts = partsGroup,
|
||||
}, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_open_bracket(p: *P, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr, flags: Expr.EFlags) anyerror!Continuation {
|
||||
// When parsing a decorator, ignore EIndex expressions since they may be
|
||||
// part of a computed property:
|
||||
//
|
||||
// class Foo {
|
||||
// @foo ['computed']() {}
|
||||
// }
|
||||
//
|
||||
// This matches the behavior of the TypeScript compiler.
|
||||
if (flags == .ts_decorator) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
// Allow "in" inside the brackets
|
||||
const old_allow_in = p.allow_in;
|
||||
p.allow_in = true;
|
||||
|
||||
const index = try p.parseExpr(.lowest);
|
||||
|
||||
p.allow_in = old_allow_in;
|
||||
|
||||
try p.lexer.expect(.t_close_bracket);
|
||||
|
||||
left.* = p.newExpr(E.Index{
|
||||
.target = left.*,
|
||||
.index = index,
|
||||
.optional_chain = old_optional_chain,
|
||||
}, left.loc);
|
||||
optional_chain.* = old_optional_chain;
|
||||
return .next;
|
||||
}
|
||||
fn t_open_paren(p: *P, level: Level, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.call)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
const list_loc = try p.parseCallArgs();
|
||||
left.* = p.newExpr(
|
||||
E.Call{
|
||||
.target = left.*,
|
||||
.args = list_loc.list,
|
||||
.close_paren_loc = list_loc.loc,
|
||||
.optional_chain = old_optional_chain,
|
||||
},
|
||||
left.loc,
|
||||
);
|
||||
optional_chain.* = old_optional_chain;
|
||||
return .next;
|
||||
}
|
||||
fn t_question(p: *P, level: Level, noalias errors: ?*DeferredErrors, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.conditional)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
|
||||
// Stop now if we're parsing one of these:
|
||||
// "(a?) => {}"
|
||||
// "(a?: b) => {}"
|
||||
// "(a?, b?) => {}"
|
||||
if (is_typescript_enabled and left.loc.start == p.latest_arrow_arg_loc.start and (p.lexer.token == .t_colon or
|
||||
p.lexer.token == .t_close_paren or p.lexer.token == .t_comma))
|
||||
{
|
||||
if (errors == null) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
errors.?.invalid_expr_after_question = p.lexer.range();
|
||||
return .done;
|
||||
}
|
||||
|
||||
const ternary = p.newExpr(E.If{
|
||||
.test_ = left.*,
|
||||
.yes = undefined,
|
||||
.no = undefined,
|
||||
}, left.loc);
|
||||
|
||||
// Allow "in" in between "?" and ":"
|
||||
const old_allow_in = p.allow_in;
|
||||
p.allow_in = true;
|
||||
|
||||
// condition ? yes : no
|
||||
// ^
|
||||
try p.parseExprWithFlags(.comma, .none, &ternary.data.e_if.yes);
|
||||
|
||||
p.allow_in = old_allow_in;
|
||||
|
||||
// condition ? yes : no
|
||||
// ^
|
||||
try p.lexer.expect(.t_colon);
|
||||
|
||||
// condition ? yes : no
|
||||
// ^
|
||||
try p.parseExprWithFlags(.comma, .none, &ternary.data.e_if.no);
|
||||
|
||||
// condition ? yes : no
|
||||
// ^
|
||||
|
||||
left.* = ternary;
|
||||
return .next;
|
||||
}
|
||||
fn t_exclamation(p: *P, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain) anyerror!Continuation {
|
||||
// Skip over TypeScript non-null assertions
|
||||
if (p.lexer.has_newline_before) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
if (!is_typescript_enabled) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
optional_chain.* = old_optional_chain;
|
||||
|
||||
return .next;
|
||||
}
|
||||
fn t_minus_minus(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (p.lexer.has_newline_before or level.gte(.postfix)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Unary{ .op = .un_post_dec, .value = left.* }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_plus_plus(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (p.lexer.has_newline_before or level.gte(.postfix)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Unary{ .op = .un_post_inc, .value = left.* }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_comma(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.comma)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_comma, .left = left.*, .right = try p.parseExpr(.comma) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_plus(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.add)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_add, .left = left.*, .right = try p.parseExpr(.add) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_plus_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_add_assign, .left = left.*, .right = try p.parseExpr(@as(Op.Level, @enumFromInt(@intFromEnum(Op.Level.assign) - 1))) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_minus(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.add)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_sub, .left = left.*, .right = try p.parseExpr(.add) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_minus_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_sub_assign, .left = left.*, .right = try p.parseExpr(Op.Level.sub(Op.Level.assign, 1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_asterisk(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.multiply)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_mul, .left = left.*, .right = try p.parseExpr(.multiply) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_asterisk_asterisk(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.exponentiation)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_pow, .left = left.*, .right = try p.parseExpr(Op.Level.exponentiation.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_asterisk_asterisk_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_pow_assign, .left = left.*, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_asterisk_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_mul_assign, .left = left.*, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_percent(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.multiply)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_rem, .left = left.*, .right = try p.parseExpr(Op.Level.multiply) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_percent_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_rem_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_slash(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.multiply)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_div, .left = left.*, .right = try p.parseExpr(Level.multiply) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_slash_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_div_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_equals_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.equals)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_loose_eq, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_exclamation_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.equals)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_loose_ne, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_equals_equals_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.equals)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_strict_eq, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_exclamation_equals_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.equals)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_strict_ne, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_less_than(p: *P, level: Level, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation {
|
||||
// TypeScript allows type arguments to be specified with angle brackets
|
||||
// inside an expression. Unlike in other languages, this unfortunately
|
||||
// appears to require backtracking to parse.
|
||||
if (is_typescript_enabled and p.trySkipTypeScriptTypeArgumentsWithBacktracking()) {
|
||||
optional_chain.* = old_optional_chain;
|
||||
return .next;
|
||||
}
|
||||
|
||||
if (level.gte(.compare)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_lt, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_less_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.compare)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_le, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_greater_than(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.compare)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_gt, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_greater_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.compare)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_ge, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_less_than_less_than(p: *P, level: Level, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation {
|
||||
// TypeScript allows type arguments to be specified with angle brackets
|
||||
// inside an expression. Unlike in other languages, this unfortunately
|
||||
// appears to require backtracking to parse.
|
||||
if (is_typescript_enabled and p.trySkipTypeScriptTypeArgumentsWithBacktracking()) {
|
||||
optional_chain.* = old_optional_chain;
|
||||
return .next;
|
||||
}
|
||||
|
||||
if (level.gte(.shift)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_shl, .left = left.*, .right = try p.parseExpr(.shift) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_less_than_less_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_shl_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_greater_than_greater_than(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.shift)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_shr, .left = left.*, .right = try p.parseExpr(.shift) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_greater_than_greater_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_shr_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_greater_than_greater_than_greater_than(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.shift)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_u_shr, .left = left.*, .right = try p.parseExpr(.shift) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_greater_than_greater_than_greater_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_u_shr_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_question_question(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.nullish_coalescing)) {
|
||||
return .done;
|
||||
}
|
||||
try p.lexer.next();
|
||||
const prev = left.*;
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_nullish_coalescing, .left = prev, .right = try p.parseExpr(.nullish_coalescing) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_question_question_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_nullish_coalescing_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_bar_bar(p: *P, level: Level, left: *Expr, flags: Expr.EFlags) anyerror!Continuation {
|
||||
if (level.gte(.logical_or)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
// Prevent "||" inside "??" from the right
|
||||
if (level.eql(.nullish_coalescing)) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
const right = try p.parseExpr(.logical_or);
|
||||
left.* = p.newExpr(E.Binary{ .op = Op.Code.bin_logical_or, .left = left.*, .right = right }, left.loc);
|
||||
|
||||
if (level.lt(.nullish_coalescing)) {
|
||||
try p.parseSuffix(left, Level.nullish_coalescing.addF(1), null, flags);
|
||||
|
||||
if (p.lexer.token == .t_question_question) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
}
|
||||
return .next;
|
||||
}
|
||||
fn t_bar_bar_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_logical_or_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_ampersand_ampersand(p: *P, level: Level, left: *Expr, flags: Expr.EFlags) anyerror!Continuation {
|
||||
if (level.gte(.logical_and)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
// Prevent "&&" inside "??" from the right
|
||||
if (level.eql(.nullish_coalescing)) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_logical_and, .left = left.*, .right = try p.parseExpr(.logical_and) }, left.loc);
|
||||
|
||||
// Prevent "&&" inside "??" from the left
|
||||
if (level.lt(.nullish_coalescing)) {
|
||||
try p.parseSuffix(left, Level.nullish_coalescing.addF(1), null, flags);
|
||||
|
||||
if (p.lexer.token == .t_question_question) {
|
||||
try p.lexer.unexpected();
|
||||
return error.SyntaxError;
|
||||
}
|
||||
}
|
||||
return .next;
|
||||
}
|
||||
fn t_ampersand_ampersand_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_logical_and_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_bar(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.bitwise_or)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_or, .left = left.*, .right = try p.parseExpr(.bitwise_or) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_bar_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_or_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_ampersand(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.bitwise_and)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_and, .left = left.*, .right = try p.parseExpr(.bitwise_and) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_ampersand_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_and_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_caret(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.bitwise_xor)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_xor, .left = left.*, .right = try p.parseExpr(.bitwise_xor) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_caret_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_xor_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.assign)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_in(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.compare) or !p.allow_in) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
// Warn about "!a in b" instead of "!(a in b)"
|
||||
switch (left.data) {
|
||||
.e_unary => |unary| {
|
||||
if (unary.op == .un_not) {
|
||||
// TODO:
|
||||
// p.log.addRangeWarning(source: ?Source, r: Range, text: string)
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_in, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
fn t_instanceof(p: *P, level: Level, left: *Expr) anyerror!Continuation {
|
||||
if (level.gte(.compare)) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
// Warn about "!a instanceof b" instead of "!(a instanceof b)". Here's an
|
||||
// example of code with this problem: https://github.com/mrdoob/three.js/pull/11182.
|
||||
if (!p.options.suppress_warnings_about_weird_code) {
|
||||
switch (left.data) {
|
||||
.e_unary => |unary| {
|
||||
if (unary.op == .un_not) {
|
||||
// TODO:
|
||||
// p.log.addRangeWarning(source: ?Source, r: Range, text: string)
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{ .op = .bin_instanceof, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc);
|
||||
return .next;
|
||||
}
|
||||
|
||||
pub fn parseSuffix(p: *P, left_and_out: *Expr, level: Level, noalias errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!void {
|
||||
var left_value = left_and_out.*;
|
||||
// Zig has a bug where it creates a new address to stack locals each & usage.
|
||||
const left = &left_value;
|
||||
|
||||
var optional_chain_: ?OptionalChain = null;
|
||||
const optional_chain = &optional_chain_;
|
||||
while (true) {
|
||||
if (p.lexer.loc().start == p.after_arrow_body_loc.start) {
|
||||
while (true) {
|
||||
switch (p.lexer.token) {
|
||||
.t_comma => {
|
||||
if (level.gte(.comma)) {
|
||||
break;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
left.* = p.newExpr(E.Binary{
|
||||
.op = .bin_comma,
|
||||
.left = left.*,
|
||||
.right = try p.parseExpr(.comma),
|
||||
}, left.loc);
|
||||
},
|
||||
else => {
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
// Stop now if this token is forbidden to follow a TypeScript "as" cast
|
||||
if (p.forbid_suffix_after_as_loc.start > -1 and p.lexer.loc().start == p.forbid_suffix_after_as_loc.start) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the optional chain flag by default. That way we won't accidentally
|
||||
// treat "c.d" as OptionalChainContinue in "a?.b + c.d".
|
||||
const old_optional_chain = optional_chain.*;
|
||||
optional_chain.* = null;
|
||||
|
||||
// Each of these tokens are split into a function to conserve
|
||||
// stack space. Currently in Zig, the compiler does not reuse
|
||||
// stack space between scopes This means that having a large
|
||||
// function with many scopes and local variables consumes
|
||||
// enormous amounts of stack space.
|
||||
const continuation = switch (p.lexer.token) {
|
||||
inline .t_ampersand,
|
||||
.t_ampersand_ampersand_equals,
|
||||
.t_ampersand_equals,
|
||||
.t_asterisk,
|
||||
.t_asterisk_asterisk,
|
||||
.t_asterisk_asterisk_equals,
|
||||
.t_asterisk_equals,
|
||||
.t_bar,
|
||||
.t_bar_bar_equals,
|
||||
.t_bar_equals,
|
||||
.t_caret,
|
||||
.t_caret_equals,
|
||||
.t_comma,
|
||||
.t_equals,
|
||||
.t_equals_equals,
|
||||
.t_equals_equals_equals,
|
||||
.t_exclamation_equals,
|
||||
.t_exclamation_equals_equals,
|
||||
.t_greater_than,
|
||||
.t_greater_than_equals,
|
||||
.t_greater_than_greater_than,
|
||||
.t_greater_than_greater_than_equals,
|
||||
.t_greater_than_greater_than_greater_than,
|
||||
.t_greater_than_greater_than_greater_than_equals,
|
||||
.t_in,
|
||||
.t_instanceof,
|
||||
.t_less_than_equals,
|
||||
.t_less_than_less_than_equals,
|
||||
.t_minus,
|
||||
.t_minus_equals,
|
||||
.t_minus_minus,
|
||||
.t_percent,
|
||||
.t_percent_equals,
|
||||
.t_plus,
|
||||
.t_plus_equals,
|
||||
.t_plus_plus,
|
||||
.t_question_question,
|
||||
.t_question_question_equals,
|
||||
.t_slash,
|
||||
.t_slash_equals,
|
||||
=> |tag| @field(@This(), @tagName(tag))(p, level, left),
|
||||
.t_exclamation => t_exclamation(p, optional_chain, old_optional_chain),
|
||||
.t_bar_bar => t_bar_bar(p, level, left, flags),
|
||||
.t_ampersand_ampersand => t_ampersand_ampersand(p, level, left, flags),
|
||||
.t_question => t_question(p, level, errors, left),
|
||||
.t_question_dot => t_question_dot(p, level, optional_chain, left),
|
||||
.t_template_head => t_template_head(p, level, optional_chain, old_optional_chain, left),
|
||||
.t_less_than => t_less_than(p, level, optional_chain, old_optional_chain, left),
|
||||
.t_open_paren => t_open_paren(p, level, optional_chain, old_optional_chain, left),
|
||||
.t_no_substitution_template_literal => t_no_substitution_template_literal(p, level, optional_chain, old_optional_chain, left),
|
||||
.t_open_bracket => t_open_bracket(p, optional_chain, old_optional_chain, left, flags),
|
||||
.t_dot => t_dot(p, optional_chain, old_optional_chain, left),
|
||||
.t_less_than_less_than => t_less_than_less_than(p, level, optional_chain, old_optional_chain, left),
|
||||
else => handleTypescriptAs(p, level),
|
||||
};
|
||||
|
||||
switch (try continuation) {
|
||||
.next => {},
|
||||
.done => break,
|
||||
}
|
||||
}
|
||||
|
||||
left_and_out.* = left_value;
|
||||
}
|
||||
};
|
||||
}
|
||||
const Continuation = enum { next, done };
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const OptionalChain = js_ast.OptionalChain;
|
||||
|
||||
const Op = js_ast.Op;
|
||||
const Level = js_ast.Op.Level;
|
||||
|
||||
const js_lexer = bun.js_lexer;
|
||||
const T = js_lexer.T;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const DeferredErrors = js_parser.DeferredErrors;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const SideEffects = js_parser.SideEffects;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
const options = js_parser.options;
|
||||
@@ -1,460 +0,0 @@
|
||||
pub fn ParseTypescript(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
const is_typescript_enabled = P.is_typescript_enabled;
|
||||
|
||||
pub fn parseTypeScriptDecorators(p: *P) ![]ExprNodeIndex {
|
||||
if (!is_typescript_enabled) {
|
||||
return &([_]ExprNodeIndex{});
|
||||
}
|
||||
|
||||
var decorators = ListManaged(ExprNodeIndex).init(p.allocator);
|
||||
while (p.lexer.token == T.t_at) {
|
||||
try p.lexer.next();
|
||||
|
||||
// Parse a new/call expression with "exprFlagTSDecorator" so we ignore
|
||||
// EIndex expressions, since they may be part of a computed property:
|
||||
//
|
||||
// class Foo {
|
||||
// @foo ['computed']() {}
|
||||
// }
|
||||
//
|
||||
// This matches the behavior of the TypeScript compiler.
|
||||
try decorators.ensureUnusedCapacity(1);
|
||||
try p.parseExprWithFlags(.new, Expr.EFlags.ts_decorator, &decorators.unusedCapacitySlice()[0]);
|
||||
decorators.items.len += 1;
|
||||
}
|
||||
|
||||
return decorators.items;
|
||||
}
|
||||
|
||||
pub fn parseTypeScriptNamespaceStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) anyerror!Stmt {
|
||||
// "namespace foo {}";
|
||||
const name_loc = p.lexer.loc();
|
||||
const name_text = p.lexer.identifier;
|
||||
try p.lexer.next();
|
||||
|
||||
// Generate the namespace object
|
||||
const ts_namespace = p.getOrCreateExportedNamespaceMembers(name_text, opts.is_export, false);
|
||||
const exported_members = ts_namespace.exported_members;
|
||||
const ns_member_data = js_ast.TSNamespaceMember.Data{ .namespace = exported_members };
|
||||
|
||||
// Declare the namespace and create the scope
|
||||
var name = LocRef{ .loc = name_loc, .ref = null };
|
||||
const scope_index = try p.pushScopeForParsePass(.entry, loc);
|
||||
p.current_scope.ts_namespace = ts_namespace;
|
||||
|
||||
const old_has_non_local_export_declare_inside_namespace = p.has_non_local_export_declare_inside_namespace;
|
||||
p.has_non_local_export_declare_inside_namespace = false;
|
||||
|
||||
// Parse the statements inside the namespace
|
||||
var stmts: ListManaged(Stmt) = ListManaged(Stmt).init(p.allocator);
|
||||
if (p.lexer.token == .t_dot) {
|
||||
const dot_loc = p.lexer.loc();
|
||||
try p.lexer.next();
|
||||
|
||||
var _opts = ParseStatementOptions{
|
||||
.is_export = true,
|
||||
.is_namespace_scope = true,
|
||||
.is_typescript_declare = opts.is_typescript_declare,
|
||||
};
|
||||
stmts.append(try p.parseTypeScriptNamespaceStmt(dot_loc, &_opts)) catch unreachable;
|
||||
} else if (opts.is_typescript_declare and p.lexer.token != .t_open_brace) {
|
||||
try p.lexer.expectOrInsertSemicolon();
|
||||
} else {
|
||||
try p.lexer.expect(.t_open_brace);
|
||||
var _opts = ParseStatementOptions{
|
||||
.is_namespace_scope = true,
|
||||
.is_typescript_declare = opts.is_typescript_declare,
|
||||
};
|
||||
stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, try p.parseStmtsUpTo(.t_close_brace, &_opts));
|
||||
try p.lexer.next();
|
||||
}
|
||||
const has_non_local_export_declare_inside_namespace = p.has_non_local_export_declare_inside_namespace;
|
||||
p.has_non_local_export_declare_inside_namespace = old_has_non_local_export_declare_inside_namespace;
|
||||
|
||||
// Add any exported members from this namespace's body as members of the
|
||||
// associated namespace object.
|
||||
for (stmts.items) |stmt| {
|
||||
switch (stmt.data) {
|
||||
.s_function => |func| {
|
||||
if (func.func.flags.contains(.is_export)) {
|
||||
const locref = func.func.name.?;
|
||||
const fn_name = p.symbols.items[locref.ref.?.inner_index].original_name;
|
||||
try exported_members.put(p.allocator, fn_name, .{
|
||||
.loc = locref.loc,
|
||||
.data = .property,
|
||||
});
|
||||
try p.ref_to_ts_namespace_member.put(
|
||||
p.allocator,
|
||||
locref.ref.?,
|
||||
.property,
|
||||
);
|
||||
}
|
||||
},
|
||||
.s_class => |class| {
|
||||
if (class.is_export) {
|
||||
const locref = class.class.class_name.?;
|
||||
const class_name = p.symbols.items[locref.ref.?.inner_index].original_name;
|
||||
try exported_members.put(p.allocator, class_name, .{
|
||||
.loc = locref.loc,
|
||||
.data = .property,
|
||||
});
|
||||
try p.ref_to_ts_namespace_member.put(
|
||||
p.allocator,
|
||||
locref.ref.?,
|
||||
.property,
|
||||
);
|
||||
}
|
||||
},
|
||||
inline .s_namespace, .s_enum => |ns| {
|
||||
if (ns.is_export) {
|
||||
if (p.ref_to_ts_namespace_member.get(ns.name.ref.?)) |member_data| {
|
||||
try exported_members.put(
|
||||
p.allocator,
|
||||
p.symbols.items[ns.name.ref.?.inner_index].original_name,
|
||||
.{
|
||||
.data = member_data,
|
||||
.loc = ns.name.loc,
|
||||
},
|
||||
);
|
||||
try p.ref_to_ts_namespace_member.put(
|
||||
p.allocator,
|
||||
ns.name.ref.?,
|
||||
member_data,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_local => |local| {
|
||||
if (local.is_export) {
|
||||
for (local.decls.slice()) |decl| {
|
||||
try p.defineExportedNamespaceBinding(
|
||||
exported_members,
|
||||
decl.binding,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Import assignments may be only used in type expressions, not value
|
||||
// expressions. If this is the case, the TypeScript compiler removes
|
||||
// them entirely from the output. That can cause the namespace itself
|
||||
// to be considered empty and thus be removed.
|
||||
var import_equal_count: usize = 0;
|
||||
for (stmts.items) |stmt| {
|
||||
switch (stmt.data) {
|
||||
.s_local => |local| {
|
||||
if (local.was_ts_import_equals and !local.is_export) {
|
||||
import_equal_count += 1;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// TypeScript omits namespaces without values. These namespaces
|
||||
// are only allowed to be used in type expressions. They are
|
||||
// allowed to be exported, but can also only be used in type
|
||||
// expressions when imported. So we shouldn't count them as a
|
||||
// real export either.
|
||||
//
|
||||
// TypeScript also strangely counts namespaces containing only
|
||||
// "export declare" statements as non-empty even though "declare"
|
||||
// statements are only type annotations. We cannot omit the namespace
|
||||
// in that case. See https://github.com/evanw/esbuild/issues/1158.
|
||||
if ((stmts.items.len == import_equal_count and !has_non_local_export_declare_inside_namespace) or opts.is_typescript_declare) {
|
||||
p.popAndDiscardScope(scope_index);
|
||||
if (opts.is_module_scope) {
|
||||
p.local_type_names.put(p.allocator, name_text, true) catch unreachable;
|
||||
}
|
||||
return p.s(S.TypeScript{}, loc);
|
||||
}
|
||||
|
||||
var arg_ref = Ref.None;
|
||||
if (!opts.is_typescript_declare) {
|
||||
// Avoid a collision with the namespace closure argument variable if the
|
||||
// namespace exports a symbol with the same name as the namespace itself:
|
||||
//
|
||||
// namespace foo {
|
||||
// export let foo = 123
|
||||
// console.log(foo)
|
||||
// }
|
||||
//
|
||||
// TypeScript generates the following code in this case:
|
||||
//
|
||||
// var foo;
|
||||
// (function (foo_1) {
|
||||
// foo_1.foo = 123;
|
||||
// console.log(foo_1.foo);
|
||||
// })(foo || (foo = {}));
|
||||
//
|
||||
if (p.current_scope.members.contains(name_text)) {
|
||||
// Add a "_" to make tests easier to read, since non-bundler tests don't
|
||||
// run the renamer. For external-facing things the renamer will avoid
|
||||
// collisions automatically so this isn't important for correctness.
|
||||
arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable;
|
||||
p.current_scope.generated.push(p.allocator, arg_ref) catch unreachable;
|
||||
} else {
|
||||
arg_ref = p.newSymbol(.hoisted, name_text) catch unreachable;
|
||||
}
|
||||
ts_namespace.arg_ref = arg_ref;
|
||||
}
|
||||
p.popScope();
|
||||
|
||||
if (!opts.is_typescript_declare) {
|
||||
name.ref = p.declareSymbol(.ts_namespace, name_loc, name_text) catch bun.outOfMemory();
|
||||
try p.ref_to_ts_namespace_member.put(p.allocator, name.ref.?, ns_member_data);
|
||||
}
|
||||
|
||||
return p.s(S.Namespace{
|
||||
.name = name,
|
||||
.arg = arg_ref,
|
||||
.stmts = stmts.items,
|
||||
.is_export = opts.is_export,
|
||||
}, loc);
|
||||
}
|
||||
|
||||
pub fn parseTypeScriptImportEqualsStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, default_name_loc: logger.Loc, default_name: string) anyerror!Stmt {
|
||||
try p.lexer.expect(.t_equals);
|
||||
|
||||
const kind = S.Local.Kind.k_const;
|
||||
const name = p.lexer.identifier;
|
||||
const target = p.newExpr(E.Identifier{ .ref = p.storeNameInRef(name) catch unreachable }, p.lexer.loc());
|
||||
var value = target;
|
||||
try p.lexer.expect(.t_identifier);
|
||||
|
||||
if (strings.eqlComptime(name, "require") and p.lexer.token == .t_open_paren) {
|
||||
// "import ns = require('x')"
|
||||
try p.lexer.next();
|
||||
const path = p.newExpr(try p.lexer.toEString(), p.lexer.loc());
|
||||
try p.lexer.expect(.t_string_literal);
|
||||
try p.lexer.expect(.t_close_paren);
|
||||
if (!opts.is_typescript_declare) {
|
||||
const args = try ExprNodeList.one(p.allocator, path);
|
||||
value = p.newExpr(E.Call{ .target = target, .close_paren_loc = p.lexer.loc(), .args = args }, loc);
|
||||
}
|
||||
} else {
|
||||
// "import Foo = Bar"
|
||||
// "import Foo = Bar.Baz"
|
||||
var prev_value = value;
|
||||
while (p.lexer.token == .t_dot) : (prev_value = value) {
|
||||
try p.lexer.next();
|
||||
value = p.newExpr(E.Dot{ .target = prev_value, .name = p.lexer.identifier, .name_loc = p.lexer.loc() }, loc);
|
||||
try p.lexer.expect(.t_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
try p.lexer.expectOrInsertSemicolon();
|
||||
|
||||
if (opts.is_typescript_declare) {
|
||||
// "import type foo = require('bar');"
|
||||
// "import type foo = bar.baz;"
|
||||
return p.s(S.TypeScript{}, loc);
|
||||
}
|
||||
|
||||
const ref = p.declareSymbol(.constant, default_name_loc, default_name) catch unreachable;
|
||||
var decls = p.allocator.alloc(Decl, 1) catch unreachable;
|
||||
decls[0] = Decl{
|
||||
.binding = p.b(B.Identifier{ .ref = ref }, default_name_loc),
|
||||
.value = value,
|
||||
};
|
||||
return p.s(S.Local{ .kind = kind, .decls = Decl.List.init(decls), .is_export = opts.is_export, .was_ts_import_equals = true }, loc);
|
||||
}
|
||||
|
||||
pub fn parseTypescriptEnumStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) anyerror!Stmt {
|
||||
try p.lexer.expect(.t_enum);
|
||||
const name_loc = p.lexer.loc();
|
||||
const name_text = p.lexer.identifier;
|
||||
try p.lexer.expect(.t_identifier);
|
||||
var name = LocRef{ .loc = name_loc, .ref = Ref.None };
|
||||
|
||||
// Generate the namespace object
|
||||
var arg_ref: Ref = undefined;
|
||||
const ts_namespace = p.getOrCreateExportedNamespaceMembers(name_text, opts.is_export, true);
|
||||
const exported_members = ts_namespace.exported_members;
|
||||
const enum_member_data = js_ast.TSNamespaceMember.Data{ .namespace = exported_members };
|
||||
|
||||
// Declare the enum and create the scope
|
||||
const scope_index = p.scopes_in_order.items.len;
|
||||
if (!opts.is_typescript_declare) {
|
||||
name.ref = try p.declareSymbol(.ts_enum, name_loc, name_text);
|
||||
_ = try p.pushScopeForParsePass(.entry, loc);
|
||||
p.current_scope.ts_namespace = ts_namespace;
|
||||
p.ref_to_ts_namespace_member.putNoClobber(p.allocator, name.ref.?, enum_member_data) catch bun.outOfMemory();
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_open_brace);
|
||||
|
||||
// Parse the body
|
||||
var values = std.ArrayList(js_ast.EnumValue).init(p.allocator);
|
||||
while (p.lexer.token != .t_close_brace) {
|
||||
var value = js_ast.EnumValue{ .loc = p.lexer.loc(), .ref = Ref.None, .name = undefined, .value = null };
|
||||
var needs_symbol = false;
|
||||
|
||||
// Parse the name
|
||||
if (p.lexer.token == .t_string_literal) {
|
||||
value.name = (try p.lexer.toUTF8EString()).slice8();
|
||||
needs_symbol = js_lexer.isIdentifier(value.name);
|
||||
} else if (p.lexer.isIdentifierOrKeyword()) {
|
||||
value.name = p.lexer.identifier;
|
||||
needs_symbol = true;
|
||||
} else {
|
||||
try p.lexer.expect(.t_identifier);
|
||||
// error early, name is still `undefined`
|
||||
return error.SyntaxError;
|
||||
}
|
||||
try p.lexer.next();
|
||||
|
||||
// Identifiers can be referenced by other values
|
||||
if (!opts.is_typescript_declare and needs_symbol) {
|
||||
value.ref = try p.declareSymbol(.other, value.loc, value.name);
|
||||
}
|
||||
|
||||
// Parse the initializer
|
||||
if (p.lexer.token == .t_equals) {
|
||||
try p.lexer.next();
|
||||
value.value = try p.parseExpr(.comma);
|
||||
}
|
||||
|
||||
values.append(value) catch unreachable;
|
||||
|
||||
exported_members.put(p.allocator, value.name, .{
|
||||
.loc = value.loc,
|
||||
.data = .enum_property,
|
||||
}) catch bun.outOfMemory();
|
||||
|
||||
if (p.lexer.token != .t_comma and p.lexer.token != .t_semicolon) {
|
||||
break;
|
||||
}
|
||||
|
||||
try p.lexer.next();
|
||||
}
|
||||
|
||||
if (!opts.is_typescript_declare) {
|
||||
// Avoid a collision with the enum closure argument variable if the
|
||||
// enum exports a symbol with the same name as the enum itself:
|
||||
//
|
||||
// enum foo {
|
||||
// foo = 123,
|
||||
// bar = foo,
|
||||
// }
|
||||
//
|
||||
// TypeScript generates the following code in this case:
|
||||
//
|
||||
// var foo;
|
||||
// (function (foo) {
|
||||
// foo[foo["foo"] = 123] = "foo";
|
||||
// foo[foo["bar"] = 123] = "bar";
|
||||
// })(foo || (foo = {}));
|
||||
//
|
||||
// Whereas in this case:
|
||||
//
|
||||
// enum foo {
|
||||
// bar = foo as any,
|
||||
// }
|
||||
//
|
||||
// TypeScript generates the following code:
|
||||
//
|
||||
// var foo;
|
||||
// (function (foo) {
|
||||
// foo[foo["bar"] = foo] = "bar";
|
||||
// })(foo || (foo = {}));
|
||||
if (p.current_scope.members.contains(name_text)) {
|
||||
// Add a "_" to make tests easier to read, since non-bundler tests don't
|
||||
// run the renamer. For external-facing things the renamer will avoid
|
||||
// collisions automatically so this isn't important for correctness.
|
||||
arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable;
|
||||
p.current_scope.generated.push(p.allocator, arg_ref) catch unreachable;
|
||||
} else {
|
||||
arg_ref = p.declareSymbol(.hoisted, name_loc, name_text) catch unreachable;
|
||||
}
|
||||
p.ref_to_ts_namespace_member.put(p.allocator, arg_ref, enum_member_data) catch bun.outOfMemory();
|
||||
ts_namespace.arg_ref = arg_ref;
|
||||
|
||||
p.popScope();
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_close_brace);
|
||||
|
||||
if (opts.is_typescript_declare) {
|
||||
if (opts.is_namespace_scope and opts.is_export) {
|
||||
p.has_non_local_export_declare_inside_namespace = true;
|
||||
}
|
||||
|
||||
return p.s(S.TypeScript{}, loc);
|
||||
}
|
||||
|
||||
// Save these for when we do out-of-order enum visiting
|
||||
//
|
||||
// Make a copy of "scopesInOrder" instead of a slice or index since
|
||||
// the original array may be flattened in the future by
|
||||
// "popAndFlattenScope"
|
||||
p.scopes_in_order_for_enum.putNoClobber(
|
||||
p.allocator,
|
||||
loc,
|
||||
scope_order_clone: {
|
||||
var count: usize = 0;
|
||||
for (p.scopes_in_order.items[scope_index..]) |i| {
|
||||
if (i != null) count += 1;
|
||||
}
|
||||
|
||||
const items = p.allocator.alloc(ScopeOrder, count) catch bun.outOfMemory();
|
||||
var i: usize = 0;
|
||||
for (p.scopes_in_order.items[scope_index..]) |item| {
|
||||
items[i] = item orelse continue;
|
||||
i += 1;
|
||||
}
|
||||
break :scope_order_clone items;
|
||||
},
|
||||
) catch bun.outOfMemory();
|
||||
|
||||
return p.s(S.Enum{
|
||||
.name = name,
|
||||
.arg = arg_ref,
|
||||
.values = values.items,
|
||||
.is_export = opts.is_export,
|
||||
}, loc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const B = js_ast.B;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeIndex = js_ast.ExprNodeIndex;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const Stmt = js_ast.Stmt;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Decl = G.Decl;
|
||||
|
||||
const js_lexer = bun.js_lexer;
|
||||
const T = js_lexer.T;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const ParseStatementOptions = js_parser.ParseStatementOptions;
|
||||
const Ref = js_parser.Ref;
|
||||
const ScopeOrder = js_parser.ScopeOrder;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
const ListManaged = std.ArrayList;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,133 +0,0 @@
|
||||
pub fn Symbols(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
|
||||
pub fn findSymbol(noalias p: *P, loc: logger.Loc, name: string) !FindSymbolResult {
|
||||
return findSymbolWithRecordUsage(p, loc, name, true);
|
||||
}
|
||||
|
||||
pub fn findSymbolWithRecordUsage(noalias p: *P, loc: logger.Loc, name: string, comptime record_usage: bool) !FindSymbolResult {
|
||||
var declare_loc: logger.Loc = logger.Loc.Empty;
|
||||
var is_inside_with_scope = false;
|
||||
// This function can show up in profiling.
|
||||
// That's part of why we do this.
|
||||
// Instead of rehashing `name` for every scope, we do it just once.
|
||||
const hash = Scope.getMemberHash(name);
|
||||
const allocator = p.allocator;
|
||||
|
||||
const ref: Ref = brk: {
|
||||
var current: ?*Scope = p.current_scope;
|
||||
|
||||
var did_forbid_arguments = false;
|
||||
|
||||
while (current) |scope| : (current = current.?.parent) {
|
||||
// Track if we're inside a "with" statement body
|
||||
if (scope.kind == .with) {
|
||||
is_inside_with_scope = true;
|
||||
}
|
||||
|
||||
// Forbid referencing "arguments" inside class bodies
|
||||
if (scope.forbid_arguments and !did_forbid_arguments and strings.eqlComptime(name, "arguments")) {
|
||||
const r = js_lexer.rangeOfIdentifier(p.source, loc);
|
||||
p.log.addRangeErrorFmt(p.source, r, allocator, "Cannot access \"{s}\" here", .{name}) catch unreachable;
|
||||
did_forbid_arguments = true;
|
||||
}
|
||||
|
||||
// Is the symbol a member of this scope?
|
||||
if (scope.getMemberWithHash(name, hash)) |member| {
|
||||
declare_loc = member.loc;
|
||||
break :brk member.ref;
|
||||
}
|
||||
|
||||
// Is the symbol a member of this scope's TypeScript namespace?
|
||||
if (scope.ts_namespace) |ts_namespace| {
|
||||
if (ts_namespace.exported_members.get(name)) |member| {
|
||||
if (member.data.isEnum() == ts_namespace.is_enum_scope) {
|
||||
declare_loc = member.loc;
|
||||
// If this is an identifier from a sibling TypeScript namespace, then we're
|
||||
// going to have to generate a property access instead of a simple reference.
|
||||
// Lazily-generate an identifier that represents this property access.
|
||||
const gop = try ts_namespace.property_accesses.getOrPut(p.allocator, name);
|
||||
if (!gop.found_existing) {
|
||||
const ref = try p.newSymbol(.other, name);
|
||||
gop.value_ptr.* = ref;
|
||||
p.symbols.items[ref.inner_index].namespace_alias = .{
|
||||
.namespace_ref = ts_namespace.arg_ref,
|
||||
.alias = name,
|
||||
};
|
||||
break :brk ref;
|
||||
}
|
||||
break :brk gop.value_ptr.*;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate an "unbound" symbol
|
||||
p.checkForNonBMPCodePoint(loc, name);
|
||||
if (comptime !record_usage) {
|
||||
return FindSymbolResult{
|
||||
.ref = Ref.None,
|
||||
.declare_loc = loc,
|
||||
.is_inside_with_scope = is_inside_with_scope,
|
||||
};
|
||||
}
|
||||
|
||||
const gpe = p.module_scope.getOrPutMemberWithHash(allocator, name, hash) catch unreachable;
|
||||
|
||||
// I don't think this happens?
|
||||
if (gpe.found_existing) {
|
||||
const existing = gpe.value_ptr.*;
|
||||
declare_loc = existing.loc;
|
||||
break :brk existing.ref;
|
||||
}
|
||||
|
||||
const _ref = p.newSymbol(.unbound, name) catch unreachable;
|
||||
|
||||
gpe.key_ptr.* = name;
|
||||
gpe.value_ptr.* = js_ast.Scope.Member{ .ref = _ref, .loc = loc };
|
||||
|
||||
declare_loc = loc;
|
||||
|
||||
break :brk _ref;
|
||||
};
|
||||
|
||||
// If we had to pass through a "with" statement body to get to the symbol
|
||||
// declaration, then this reference could potentially also refer to a
|
||||
// property on the target object of the "with" statement. We must not rename
|
||||
// it or we risk changing the behavior of the code.
|
||||
if (is_inside_with_scope) {
|
||||
p.symbols.items[ref.innerIndex()].must_not_be_renamed = true;
|
||||
}
|
||||
|
||||
// Track how many times we've referenced this symbol
|
||||
if (comptime record_usage) p.recordUsage(ref);
|
||||
|
||||
return FindSymbolResult{
|
||||
.ref = ref,
|
||||
.declare_loc = declare_loc,
|
||||
.is_inside_with_scope = is_inside_with_scope,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const js_lexer = bun.js_lexer;
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const Scope = js_ast.Scope;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const FindSymbolResult = js_parser.FindSymbolResult;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const Ref = js_parser.Ref;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
1367
src/ast/visit.zig
1367
src/ast/visit.zig
File diff suppressed because it is too large
Load Diff
@@ -1,451 +0,0 @@
|
||||
pub fn CreateBinaryExpressionVisitor(
|
||||
comptime parser_feature__typescript: bool,
|
||||
comptime parser_feature__jsx: JSXTransformType,
|
||||
comptime parser_feature__scan_only: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||||
|
||||
pub const BinaryExpressionVisitor = struct {
|
||||
e: *E.Binary,
|
||||
loc: logger.Loc,
|
||||
in: ExprIn,
|
||||
|
||||
/// Input for visiting the left child
|
||||
left_in: ExprIn,
|
||||
|
||||
/// "Local variables" passed from "checkAndPrepare" to "visitRightAndFinish"
|
||||
is_stmt_expr: bool = false,
|
||||
|
||||
pub fn visitRightAndFinish(
|
||||
v: *BinaryExpressionVisitor,
|
||||
p: *P,
|
||||
) Expr {
|
||||
var e_ = v.e;
|
||||
const is_call_target = @as(Expr.Tag, p.call_target) == .e_binary and e_ == p.call_target.e_binary;
|
||||
// const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and expr.data.e_binary == p.stmt_expr_value.e_binary;
|
||||
const was_anonymous_named_expr = e_.right.isAnonymousNamed();
|
||||
|
||||
// Mark the control flow as dead if the branch is never taken
|
||||
switch (e_.op) {
|
||||
.bin_logical_or => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
if (side_effects.ok and side_effects.value) {
|
||||
// "true || dead"
|
||||
const old = p.is_control_flow_dead;
|
||||
p.is_control_flow_dead = true;
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
p.is_control_flow_dead = old;
|
||||
} else {
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
}
|
||||
},
|
||||
.bin_logical_and => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
if (side_effects.ok and !side_effects.value) {
|
||||
// "false && dead"
|
||||
const old = p.is_control_flow_dead;
|
||||
p.is_control_flow_dead = true;
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
p.is_control_flow_dead = old;
|
||||
} else {
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
}
|
||||
},
|
||||
.bin_nullish_coalescing => {
|
||||
const side_effects = SideEffects.toNullOrUndefined(p, e_.left.data);
|
||||
if (side_effects.ok and !side_effects.value) {
|
||||
// "notNullOrUndefined ?? dead"
|
||||
const old = p.is_control_flow_dead;
|
||||
p.is_control_flow_dead = true;
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
p.is_control_flow_dead = old;
|
||||
} else {
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
}
|
||||
},
|
||||
else => {
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
},
|
||||
}
|
||||
|
||||
// Always put constants on the right for equality comparisons to help
|
||||
// reduce the number of cases we have to check during pattern matching. We
|
||||
// can only reorder expressions that do not have any side effects.
|
||||
switch (e_.op) {
|
||||
.bin_loose_eq, .bin_loose_ne, .bin_strict_eq, .bin_strict_ne => {
|
||||
if (SideEffects.isPrimitiveToReorder(e_.left.data) and !SideEffects.isPrimitiveToReorder(e_.right.data)) {
|
||||
const _left = e_.left;
|
||||
const _right = e_.right;
|
||||
e_.left = _right;
|
||||
e_.right = _left;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (e_.op) {
|
||||
.bin_comma => {
|
||||
// "(1, 2)" => "2"
|
||||
// "(sideEffects(), 2)" => "(sideEffects(), 2)"
|
||||
// "(0, this.fn)" => "this.fn"
|
||||
// "(0, this.fn)()" => "(0, this.fn)()"
|
||||
if (p.options.features.minify_syntax) {
|
||||
if (SideEffects.simplifyUnusedExpr(p, e_.left)) |simplified_left| {
|
||||
e_.left = simplified_left;
|
||||
} else {
|
||||
// The left operand has no side effects, but we need to preserve
|
||||
// the comma operator semantics when used as a call target
|
||||
if (is_call_target and e_.right.hasValueForThisInCall()) {
|
||||
// Keep the comma expression to strip "this" binding
|
||||
e_.left = Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc };
|
||||
} else {
|
||||
return e_.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_loose_eq => {
|
||||
const equality = e_.left.data.eql(e_.right.data, p, .loose);
|
||||
if (equality.ok) {
|
||||
if (equality.is_require_main_and_module) {
|
||||
p.ignoreUsageOfRuntimeRequire();
|
||||
p.ignoreUsage(p.module_ref);
|
||||
return p.valueForImportMetaMain(false, v.loc);
|
||||
}
|
||||
|
||||
return p.newExpr(
|
||||
E.Boolean{ .value = equality.equal },
|
||||
v.loc,
|
||||
);
|
||||
}
|
||||
|
||||
if (p.options.features.minify_syntax) {
|
||||
// "x == void 0" => "x == null"
|
||||
if (e_.left.data == .e_undefined) {
|
||||
e_.left.data = .{ .e_null = E.Null{} };
|
||||
} else if (e_.right.data == .e_undefined) {
|
||||
e_.right.data = .{ .e_null = E.Null{} };
|
||||
}
|
||||
}
|
||||
|
||||
// const after_op_loc = locAfterOp(e_.);
|
||||
// TODO: warn about equality check
|
||||
// TODO: warn about typeof string
|
||||
|
||||
},
|
||||
.bin_strict_eq => {
|
||||
const equality = e_.left.data.eql(e_.right.data, p, .strict);
|
||||
if (equality.ok) {
|
||||
if (equality.is_require_main_and_module) {
|
||||
p.ignoreUsage(p.module_ref);
|
||||
p.ignoreUsageOfRuntimeRequire();
|
||||
return p.valueForImportMetaMain(false, v.loc);
|
||||
}
|
||||
|
||||
return p.newExpr(E.Boolean{ .value = equality.equal }, v.loc);
|
||||
}
|
||||
|
||||
// const after_op_loc = locAfterOp(e_.);
|
||||
// TODO: warn about equality check
|
||||
// TODO: warn about typeof string
|
||||
},
|
||||
.bin_loose_ne => {
|
||||
const equality = e_.left.data.eql(e_.right.data, p, .loose);
|
||||
if (equality.ok) {
|
||||
if (equality.is_require_main_and_module) {
|
||||
p.ignoreUsage(p.module_ref);
|
||||
p.ignoreUsageOfRuntimeRequire();
|
||||
return p.valueForImportMetaMain(true, v.loc);
|
||||
}
|
||||
|
||||
return p.newExpr(E.Boolean{ .value = !equality.equal }, v.loc);
|
||||
}
|
||||
// const after_op_loc = locAfterOp(e_.);
|
||||
// TODO: warn about equality check
|
||||
// TODO: warn about typeof string
|
||||
|
||||
// "x != void 0" => "x != null"
|
||||
if (@as(Expr.Tag, e_.right.data) == .e_undefined) {
|
||||
e_.right = p.newExpr(E.Null{}, e_.right.loc);
|
||||
}
|
||||
},
|
||||
.bin_strict_ne => {
|
||||
const equality = e_.left.data.eql(e_.right.data, p, .strict);
|
||||
if (equality.ok) {
|
||||
if (equality.is_require_main_and_module) {
|
||||
p.ignoreUsage(p.module_ref);
|
||||
p.ignoreUsageOfRuntimeRequire();
|
||||
return p.valueForImportMetaMain(true, v.loc);
|
||||
}
|
||||
|
||||
return p.newExpr(E.Boolean{ .value = !equality.equal }, v.loc);
|
||||
}
|
||||
},
|
||||
.bin_nullish_coalescing => {
|
||||
const nullorUndefined = SideEffects.toNullOrUndefined(p, e_.left.data);
|
||||
if (nullorUndefined.ok) {
|
||||
if (!nullorUndefined.value) {
|
||||
return e_.left;
|
||||
} else if (nullorUndefined.side_effects == .no_side_effects) {
|
||||
// "(null ?? fn)()" => "fn()"
|
||||
// "(null ?? this.fn)" => "this.fn"
|
||||
// "(null ?? this.fn)()" => "(0, this.fn)()"
|
||||
if (is_call_target and e_.right.hasValueForThisInCall()) {
|
||||
return Expr.joinWithComma(Expr{ .data = .{ .e_number = .{ .value = 0.0 } }, .loc = e_.left.loc }, e_.right, p.allocator);
|
||||
}
|
||||
|
||||
return e_.right;
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_logical_or => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
if (side_effects.ok and side_effects.value) {
|
||||
return e_.left;
|
||||
} else if (side_effects.ok and side_effects.side_effects == .no_side_effects) {
|
||||
// "(0 || fn)()" => "fn()"
|
||||
// "(0 || this.fn)" => "this.fn"
|
||||
// "(0 || this.fn)()" => "(0, this.fn)()"
|
||||
if (is_call_target and e_.right.hasValueForThisInCall()) {
|
||||
return Expr.joinWithComma(Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc }, e_.right, p.allocator);
|
||||
}
|
||||
|
||||
return e_.right;
|
||||
}
|
||||
},
|
||||
.bin_logical_and => {
|
||||
const side_effects = SideEffects.toBoolean(p, e_.left.data);
|
||||
if (side_effects.ok) {
|
||||
if (!side_effects.value) {
|
||||
return e_.left;
|
||||
} else if (side_effects.side_effects == .no_side_effects) {
|
||||
// "(1 && fn)()" => "fn()"
|
||||
// "(1 && this.fn)" => "this.fn"
|
||||
// "(1 && this.fn)()" => "(0, this.fn)()"
|
||||
if (is_call_target and e_.right.hasValueForThisInCall()) {
|
||||
return Expr.joinWithComma(Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc }, e_.right, p.allocator);
|
||||
}
|
||||
|
||||
return e_.right;
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_add => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{ .value = vals[0] + vals[1] }, v.loc);
|
||||
}
|
||||
|
||||
// "'abc' + 'xyz'" => "'abcxyz'"
|
||||
if (foldStringAddition(e_.left, e_.right, p.allocator, .normal)) |res| {
|
||||
return res;
|
||||
}
|
||||
|
||||
// "(x + 'abc') + 'xyz'" => "'abcxyz'"
|
||||
if (e_.left.data.as(.e_binary)) |left| {
|
||||
if (left.op == .bin_add) {
|
||||
if (foldStringAddition(left.right, e_.right, p.allocator, .nested_left)) |result| {
|
||||
return p.newExpr(E.Binary{
|
||||
.left = left.left,
|
||||
.right = result,
|
||||
.op = .bin_add,
|
||||
}, e_.left.loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_sub => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{ .value = vals[0] - vals[1] }, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_mul => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{ .value = vals[0] * vals[1] }, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_div => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{ .value = vals[0] / vals[1] }, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_rem => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
const fmod = @extern(*const fn (f64, f64) callconv(.C) f64, .{ .name = "fmod" });
|
||||
return p.newExpr(
|
||||
// Use libc fmod here to be consistent with what JavaScriptCore does
|
||||
// https://github.com/oven-sh/WebKit/blob/7a0b13626e5db69aa5a32d037431d381df5dfb61/Source/JavaScriptCore/runtime/MathCommon.cpp#L574-L597
|
||||
E.Number{ .value = if (comptime Environment.isNative) fmod(vals[0], vals[1]) else std.math.mod(f64, vals[0], vals[1]) catch 0 },
|
||||
v.loc,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_pow => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{ .value = jsc.math.pow(vals[0], vals[1]) }, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_shl => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
const left = floatToInt32(vals[0]);
|
||||
const right: u8 = @intCast(@as(u32, @bitCast(floatToInt32(vals[1]))) % 32);
|
||||
const result: i32 = @bitCast(std.math.shl(i32, left, right));
|
||||
return p.newExpr(E.Number{
|
||||
.value = @floatFromInt(result),
|
||||
}, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_shr => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
const left = floatToInt32(vals[0]);
|
||||
const right: u8 = @intCast(@as(u32, @bitCast(floatToInt32(vals[1]))) % 32);
|
||||
const result: i32 = @bitCast(std.math.shr(i32, left, right));
|
||||
return p.newExpr(E.Number{
|
||||
.value = @floatFromInt(result),
|
||||
}, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_u_shr => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
const left: u32 = @bitCast(floatToInt32(vals[0]));
|
||||
const right: u8 = @intCast(@as(u32, @bitCast(floatToInt32(vals[1]))) % 32);
|
||||
const result: u32 = std.math.shr(u32, left, right);
|
||||
return p.newExpr(E.Number{
|
||||
.value = @floatFromInt(result),
|
||||
}, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_bitwise_and => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{
|
||||
.value = @floatFromInt((floatToInt32(vals[0]) & floatToInt32(vals[1]))),
|
||||
}, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_bitwise_or => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{
|
||||
.value = @floatFromInt((floatToInt32(vals[0]) | floatToInt32(vals[1]))),
|
||||
}, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_bitwise_xor => {
|
||||
if (p.should_fold_typescript_constant_expressions) {
|
||||
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
|
||||
return p.newExpr(E.Number{
|
||||
.value = @floatFromInt((floatToInt32(vals[0]) ^ floatToInt32(vals[1]))),
|
||||
}, v.loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
// ---------------------------------------------------------------------------------------------------
|
||||
.bin_assign => {
|
||||
// Optionally preserve the name
|
||||
if (e_.left.data == .e_identifier) {
|
||||
e_.right = p.maybeKeepExprSymbolName(e_.right, p.symbols.items[e_.left.data.e_identifier.ref.innerIndex()].original_name, was_anonymous_named_expr);
|
||||
}
|
||||
},
|
||||
.bin_nullish_coalescing_assign, .bin_logical_or_assign => {
|
||||
// Special case `{}.field ??= value` to minify to `value`
|
||||
// This optimization is specifically to target this pattern in HMR:
|
||||
// `import.meta.hot.data.etc ??= init()`
|
||||
if (e_.left.data.as(.e_dot)) |dot| {
|
||||
if (dot.target.data.as(.e_object)) |obj| {
|
||||
if (obj.properties.len == 0) {
|
||||
if (!bun.strings.eqlComptime(dot.name, "__proto__"))
|
||||
return e_.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Expr{ .loc = v.loc, .data = .{ .e_binary = e_ } };
|
||||
}
|
||||
|
||||
pub fn checkAndPrepare(v: *BinaryExpressionVisitor, p: *P) ?Expr {
|
||||
var e_ = v.e;
|
||||
switch (e_.left.data) {
|
||||
// Special-case private identifiers
|
||||
.e_private_identifier => |_private| {
|
||||
if (e_.op == .bin_in) {
|
||||
var private = _private;
|
||||
const name = p.loadNameFromRef(private.ref);
|
||||
const result = p.findSymbol(e_.left.loc, name) catch unreachable;
|
||||
private.ref = result.ref;
|
||||
|
||||
// Unlike regular identifiers, there are no unbound private identifiers
|
||||
const kind: Symbol.Kind = p.symbols.items[result.ref.innerIndex()].kind;
|
||||
if (!Symbol.isKindPrivate(kind)) {
|
||||
const r = logger.Range{ .loc = e_.left.loc, .len = @as(i32, @intCast(name.len)) };
|
||||
p.log.addRangeErrorFmt(p.source, r, p.allocator, "Private name \"{s}\" must be declared in an enclosing class", .{name}) catch unreachable;
|
||||
}
|
||||
|
||||
e_.right = p.visitExpr(e_.right);
|
||||
e_.left = .{ .data = .{ .e_private_identifier = private }, .loc = e_.left.loc };
|
||||
|
||||
// privateSymbolNeedsToBeLowered
|
||||
return Expr{ .loc = v.loc, .data = .{ .e_binary = e_ } };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
v.is_stmt_expr = p.stmt_expr_value == .e_binary and p.stmt_expr_value.e_binary == e_;
|
||||
|
||||
v.left_in = ExprIn{
|
||||
.assign_target = e_.op.binaryAssignTarget(),
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const jsc = bun.jsc;
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const ExprIn = js_parser.ExprIn;
|
||||
const JSXTransformType = js_parser.JSXTransformType;
|
||||
const Prefill = js_parser.Prefill;
|
||||
const SideEffects = js_parser.SideEffects;
|
||||
const floatToInt32 = js_parser.floatToInt32;
|
||||
const foldStringAddition = js_parser.foldStringAddition;
|
||||
const options = js_parser.options;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ pub const Loop = uws.Loop;
|
||||
pub const KeepAlive = struct {
|
||||
status: Status = .inactive,
|
||||
|
||||
const log = Output.scoped(.KeepAlive, .visible);
|
||||
const log = Output.scoped(.KeepAlive, false);
|
||||
|
||||
const Status = enum { active, inactive, done };
|
||||
|
||||
@@ -541,7 +541,7 @@ pub const FilePoll = struct {
|
||||
pending_free_head: ?*FilePoll = null,
|
||||
pending_free_tail: ?*FilePoll = null,
|
||||
|
||||
const log = Output.scoped(.FilePoll, .visible);
|
||||
const log = Output.scoped(.FilePoll, false);
|
||||
|
||||
pub fn init() Store {
|
||||
return .{
|
||||
|
||||
@@ -3,7 +3,7 @@ pub const Loop = uv.Loop;
|
||||
pub const KeepAlive = struct {
|
||||
status: Status = .inactive,
|
||||
|
||||
const log = Output.scoped(.KeepAlive, .visible);
|
||||
const log = Output.scoped(.KeepAlive, false);
|
||||
|
||||
const Status = enum { active, inactive, done };
|
||||
|
||||
@@ -121,7 +121,7 @@ pub const FilePoll = struct {
|
||||
pub const Flags = Posix.FilePoll.Flags;
|
||||
pub const Owner = Posix.FilePoll.Owner;
|
||||
|
||||
const log = Output.scoped(.FilePoll, .visible);
|
||||
const log = Output.scoped(.FilePoll, false);
|
||||
|
||||
pub inline fn isActive(this: *const FilePoll) bool {
|
||||
return this.flags.contains(.has_incremented_poll_count);
|
||||
@@ -305,7 +305,7 @@ pub const FilePoll = struct {
|
||||
pending_free_head: ?*FilePoll = null,
|
||||
pending_free_tail: ?*FilePoll = null,
|
||||
|
||||
const log = Output.scoped(.FilePoll, .visible);
|
||||
const log = Output.scoped(.FilePoll, false);
|
||||
|
||||
pub fn init() Store {
|
||||
return .{
|
||||
|
||||
45
src/bake.zig
45
src/bake.zig
@@ -27,6 +27,9 @@ pub const UserOptions = struct {
|
||||
|
||||
/// Currently, this function must run at the top of the event loop.
|
||||
pub fn fromJS(config: JSValue, global: *jsc.JSGlobalObject) !UserOptions {
|
||||
if (!config.isObject()) {
|
||||
return global.throwInvalidArguments("'" ++ api_name ++ "' is not an object", .{});
|
||||
}
|
||||
var arena = std.heap.ArenaAllocator.init(bun.default_allocator);
|
||||
errdefer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
@@ -35,38 +38,6 @@ pub const UserOptions = struct {
|
||||
errdefer allocations.free();
|
||||
var bundler_options = SplitBundlerOptions.empty;
|
||||
|
||||
if (!config.isObject()) {
|
||||
// Allow users to do `export default { app: 'react' }` for convenience
|
||||
if (config.isString()) {
|
||||
const bunstr = try config.toBunString(global);
|
||||
defer bunstr.deref();
|
||||
const utf8_string = bunstr.toUTF8(bun.default_allocator);
|
||||
defer utf8_string.deinit();
|
||||
|
||||
if (bun.strings.eql(utf8_string.byteSlice(), "react")) {
|
||||
const root = bun.getcwdAlloc(alloc) catch |err| switch (err) {
|
||||
error.OutOfMemory => {
|
||||
return global.throwOutOfMemory();
|
||||
},
|
||||
else => {
|
||||
return global.throwError(err, "while querying current working directory");
|
||||
},
|
||||
};
|
||||
|
||||
const framework = try Framework.react(alloc);
|
||||
|
||||
return UserOptions{
|
||||
.arena = arena,
|
||||
.allocations = allocations,
|
||||
.root = try alloc.dupeZ(u8, root),
|
||||
.framework = framework,
|
||||
.bundler_options = bundler_options,
|
||||
};
|
||||
}
|
||||
}
|
||||
return global.throwInvalidArguments("'" ++ api_name ++ "' is not an object", .{});
|
||||
}
|
||||
|
||||
if (try config.getOptional(global, "bundlerOptions", JSValue)) |js_options| {
|
||||
if (try js_options.getOptional(global, "server", JSValue)) |server_options| {
|
||||
bundler_options.server = try BuildConfigSubset.fromJS(global, server_options);
|
||||
@@ -750,12 +721,7 @@ pub const Framework = struct {
|
||||
out.options.react_fast_refresh = mode == .development and renderer == .client and framework.react_fast_refresh != null;
|
||||
out.options.server_components = framework.server_components != null;
|
||||
|
||||
out.options.conditions = try bun.options.ESMConditions.init(
|
||||
arena,
|
||||
out.options.target.defaultConditions(),
|
||||
out.options.target.isServerSide(),
|
||||
bundler_options.conditions.keys(),
|
||||
);
|
||||
out.options.conditions = try bun.options.ESMConditions.init(arena, out.options.target.defaultConditions());
|
||||
if (renderer == .server and framework.server_components != null) {
|
||||
try out.options.conditions.appendSlice(&.{"react-server"});
|
||||
}
|
||||
@@ -768,6 +734,9 @@ pub const Framework = struct {
|
||||
if (renderer == .server or renderer == .ssr) {
|
||||
try out.options.conditions.appendSlice(&.{"node"});
|
||||
}
|
||||
if (bundler_options.conditions.count() > 0) {
|
||||
try out.options.conditions.appendSlice(bundler_options.conditions.keys());
|
||||
}
|
||||
|
||||
out.options.production = mode != .development;
|
||||
out.options.tree_shaking = mode != .development;
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
#include "headers-handwritten.h"
|
||||
|
||||
namespace Bake {
|
||||
} // namespace Bake
|
||||
} // namespace Bake
|
||||
@@ -1,6 +1,5 @@
|
||||
// clang-format off
|
||||
#include "BakeSourceProvider.h"
|
||||
#include "DevServerSourceProvider.h"
|
||||
#include "BakeGlobalObject.h"
|
||||
#include "JavaScriptCore/CallData.h"
|
||||
#include "JavaScriptCore/Completion.h"
|
||||
@@ -79,34 +78,6 @@ extern "C" JSC::EncodedJSValue BakeLoadServerHmrPatch(GlobalObject* global, BunS
|
||||
return JSC::JSValue::encode(result);
|
||||
}
|
||||
|
||||
extern "C" JSC::EncodedJSValue BakeLoadServerHmrPatchWithSourceMap(GlobalObject* global, BunString source, const char* sourceMapJSONPtr, size_t sourceMapJSONLength) {
|
||||
JSC::VM&vm = global->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
String string = "bake://server.patch.js"_s;
|
||||
JSC::SourceOrigin origin = JSC::SourceOrigin(WTF::URL(string));
|
||||
|
||||
// Use DevServerSourceProvider with the source map JSON
|
||||
auto provider = DevServerSourceProvider::create(
|
||||
global,
|
||||
source.toWTFString(),
|
||||
sourceMapJSONPtr,
|
||||
sourceMapJSONLength,
|
||||
origin,
|
||||
WTFMove(string),
|
||||
WTF::TextPosition(),
|
||||
JSC::SourceProviderSourceType::Program
|
||||
);
|
||||
|
||||
JSC::SourceCode sourceCode = JSC::SourceCode(provider);
|
||||
|
||||
JSC::JSValue result = vm.interpreter.executeProgram(sourceCode, global, global);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
RELEASE_ASSERT(result);
|
||||
return JSC::JSValue::encode(result);
|
||||
}
|
||||
|
||||
extern "C" JSC::EncodedJSValue BakeGetModuleNamespace(
|
||||
JSC::JSGlobalObject* global,
|
||||
JSC::JSValue keyValue
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
const DevServer = @This();
|
||||
|
||||
pub const debug = bun.Output.Scoped(.DevServer, .visible);
|
||||
pub const igLog = bun.Output.scoped(.IncrementalGraph, .visible);
|
||||
pub const mapLog = bun.Output.scoped(.SourceMapStore, .visible);
|
||||
pub const debug = bun.Output.Scoped(.DevServer, false);
|
||||
pub const igLog = bun.Output.scoped(.IncrementalGraph, false);
|
||||
pub const mapLog = bun.Output.scoped(.SourceMapStore, false);
|
||||
|
||||
pub const Options = struct {
|
||||
/// Arena must live until DevServer.deinit()
|
||||
@@ -63,10 +63,10 @@ server: ?bun.jsc.API.AnyServer,
|
||||
router: FrameworkRouter,
|
||||
/// Every navigatable route has bundling state here.
|
||||
route_bundles: ArrayListUnmanaged(RouteBundle),
|
||||
/// All access into IncrementalGraph is guarded by a ThreadLock. This is
|
||||
/// All access into IncrementalGraph is guarded by a DebugThreadLock. This is
|
||||
/// only a debug assertion as contention to this is always a bug; If a bundle is
|
||||
/// active and a file is changed, that change is placed into the next bundle.
|
||||
graph_safety_lock: bun.safety.ThreadLock,
|
||||
graph_safety_lock: bun.DebugThreadLock,
|
||||
client_graph: IncrementalGraph(.client),
|
||||
server_graph: IncrementalGraph(.server),
|
||||
/// State populated during bundling and hot updates. Often cleared
|
||||
@@ -205,6 +205,8 @@ deferred_request_pool: bun.HiveArray(DeferredRequest.Node, DeferredRequest.max_p
|
||||
/// UWS can handle closing the websocket connections themselves
|
||||
active_websocket_connections: std.AutoHashMapUnmanaged(*HmrSocket, void),
|
||||
|
||||
relative_path_buf: DebugGuardedValue(bun.PathBuffer),
|
||||
|
||||
// Debugging
|
||||
|
||||
dump_dir: if (bun.FeatureFlags.bake_debugging_features) ?std.fs.Dir else void,
|
||||
@@ -282,7 +284,7 @@ pub fn init(options: Options) bun.JSOOM!*DevServer {
|
||||
.server_fetch_function_callback = .empty,
|
||||
.server_register_update_callback = .empty,
|
||||
.generation = 0,
|
||||
.graph_safety_lock = .initUnlocked(),
|
||||
.graph_safety_lock = .unlocked,
|
||||
.dump_dir = dump_dir,
|
||||
.framework = options.framework,
|
||||
.bundler_options = options.bundler_options,
|
||||
@@ -333,6 +335,7 @@ pub fn init(options: Options) bun.JSOOM!*DevServer {
|
||||
.watcher_atomics = undefined,
|
||||
.log = undefined,
|
||||
.deferred_request_pool = undefined,
|
||||
.relative_path_buf = .init(undefined, bun.DebugThreadLock.unlocked),
|
||||
});
|
||||
errdefer bun.destroy(dev);
|
||||
const allocator = dev.allocation_scope.allocator();
|
||||
@@ -563,6 +566,7 @@ pub fn deinit(dev: *DevServer) void {
|
||||
.server = {},
|
||||
.server_transpiler = {},
|
||||
.ssr_transpiler = {},
|
||||
.relative_path_buf = {},
|
||||
.vm = {},
|
||||
|
||||
// WebSockets should be deinitialized before other parts
|
||||
@@ -839,7 +843,6 @@ fn onJsRequest(dev: *DevServer, req: *Request, resp: AnyResponse) void {
|
||||
arena.allocator(),
|
||||
source_id.kind,
|
||||
dev.allocator,
|
||||
.client,
|
||||
) catch bun.outOfMemory();
|
||||
const response = StaticRoute.initFromAnyBlob(&.fromOwnedSlice(dev.allocator, json_bytes), .{
|
||||
.server = dev.server,
|
||||
@@ -951,7 +954,7 @@ fn ensureRouteIsBundled(
|
||||
dev: *DevServer,
|
||||
route_bundle_index: RouteBundle.Index,
|
||||
kind: DeferredRequest.Handler.Kind,
|
||||
req: ReqOrSaved,
|
||||
req: *Request,
|
||||
resp: AnyResponse,
|
||||
) bun.JSError!void {
|
||||
assert(dev.magic == .valid);
|
||||
@@ -1066,60 +1069,35 @@ fn ensureRouteIsBundled(
|
||||
);
|
||||
},
|
||||
.loaded => switch (kind) {
|
||||
.server_handler => try dev.onFrameworkRequestWithBundle(route_bundle_index, if (req == .req) .{ .stack = req.req } else .{ .saved = req.saved }, resp),
|
||||
.bundled_html_page => dev.onHtmlRequestWithBundle(route_bundle_index, resp, req.method()),
|
||||
.server_handler => try dev.onFrameworkRequestWithBundle(route_bundle_index, .{ .stack = req }, resp),
|
||||
.bundled_html_page => dev.onHtmlRequestWithBundle(route_bundle_index, resp, bun.http.Method.which(req.method()) orelse .POST),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const ReqOrSaved = union(enum) {
|
||||
req: *Request,
|
||||
saved: bun.jsc.API.SavedRequest,
|
||||
|
||||
pub fn method(this: *const @This()) bun.http.Method {
|
||||
return switch (this.*) {
|
||||
.req => |req| bun.http.Method.which(req.method()) orelse .POST,
|
||||
.saved => |saved| saved.request.method,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn deferRequest(
|
||||
dev: *DevServer,
|
||||
requests_array: *DeferredRequest.List,
|
||||
route_bundle_index: RouteBundle.Index,
|
||||
kind: DeferredRequest.Handler.Kind,
|
||||
req: ReqOrSaved,
|
||||
req: *Request,
|
||||
resp: AnyResponse,
|
||||
) !void {
|
||||
const deferred = dev.deferred_request_pool.get();
|
||||
debug.log("DeferredRequest(0x{x}).init", .{@intFromPtr(&deferred.data)});
|
||||
const method = req.method();
|
||||
const method = bun.http.Method.which(req.method()) orelse .POST;
|
||||
deferred.data = .{
|
||||
.route_bundle_index = route_bundle_index,
|
||||
.dev = dev,
|
||||
.ref_count = .init(),
|
||||
.handler = switch (kind) {
|
||||
.bundled_html_page => brk: {
|
||||
resp.onAborted(*DeferredRequest, DeferredRequest.onAbort, &deferred.data);
|
||||
break :brk .{ .bundled_html_page = .{ .response = resp, .method = method } };
|
||||
},
|
||||
.server_handler => brk: {
|
||||
const server_handler = switch (req) {
|
||||
.req => |r| dev.server.?.prepareAndSaveJsRequestContext(r, resp, dev.vm.global, method) orelse {
|
||||
dev.deferred_request_pool.put(deferred);
|
||||
return;
|
||||
},
|
||||
.saved => |saved| saved,
|
||||
};
|
||||
server_handler.ctx.setAbortCallback(DeferredRequest.onAbortWrapper, &deferred.data);
|
||||
break :brk .{
|
||||
.server_handler = server_handler,
|
||||
};
|
||||
.bundled_html_page => .{ .bundled_html_page = .{ .response = resp, .method = method } },
|
||||
.server_handler => .{
|
||||
.server_handler = dev.server.?.prepareAndSaveJsRequestContext(req, resp, dev.vm.global, method) orelse return,
|
||||
},
|
||||
},
|
||||
};
|
||||
deferred.data.ref();
|
||||
resp.onAborted(*DeferredRequest, DeferredRequest.onAbort, &deferred.data);
|
||||
requests_array.prepend(deferred);
|
||||
}
|
||||
|
||||
@@ -1207,7 +1185,7 @@ fn onFrameworkRequestWithBundle(
|
||||
const route_bundle = dev.routeBundlePtr(route_bundle_index);
|
||||
assert(route_bundle.data == .framework);
|
||||
|
||||
const framework_bundle = &route_bundle.data.framework;
|
||||
const bundle = &route_bundle.data.framework;
|
||||
|
||||
// Extract route params by re-matching the URL
|
||||
var params: FrameworkRouter.MatchedParams = undefined;
|
||||
@@ -1256,7 +1234,7 @@ fn onFrameworkRequestWithBundle(
|
||||
const value_str = bun.String.cloneUTF8(param.value);
|
||||
defer value_str.deref();
|
||||
|
||||
_ = try obj.putBunStringOneOrArray(global, &key_str, value_str.toJS(global));
|
||||
obj.put(global, key_str, value_str.toJS(global));
|
||||
}
|
||||
break :blk obj;
|
||||
} else JSValue.null;
|
||||
@@ -1264,64 +1242,46 @@ fn onFrameworkRequestWithBundle(
|
||||
const server_request_callback = dev.server_fetch_function_callback.get() orelse
|
||||
unreachable; // did not initialize server code
|
||||
|
||||
const router_type = dev.router.typePtr(dev.router.routePtr(framework_bundle.route_index).type);
|
||||
const router_type = dev.router.typePtr(dev.router.routePtr(bundle.route_index).type);
|
||||
|
||||
// FIXME: We should not create these on every single request
|
||||
// Wrapper functions for AsyncLocalStorage that match JSHostFnZig signature
|
||||
const SetAsyncLocalStorageWrapper = struct {
|
||||
pub fn call(global: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
||||
return VirtualMachine.VirtualMachine__setDevServerAsyncLocalStorage(global, callframe);
|
||||
}
|
||||
};
|
||||
|
||||
const GetAsyncLocalStorageWrapper = struct {
|
||||
pub fn call(global: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
||||
return VirtualMachine.VirtualMachine__getDevServerAsyncLocalStorage(global, callframe);
|
||||
}
|
||||
};
|
||||
|
||||
// Create the setter and getter functions for AsyncLocalStorage
|
||||
const setAsyncLocalStorage = jsc.JSFunction.create(dev.vm.global, "setDevServerAsyncLocalStorage", SetAsyncLocalStorageWrapper.call, 1, .{});
|
||||
const getAsyncLocalStorage = jsc.JSFunction.create(dev.vm.global, "getDevServerAsyncLocalStorage", GetAsyncLocalStorageWrapper.call, 0, .{});
|
||||
|
||||
dev.server.?.onSavedRequest(
|
||||
dev.server.?.onRequestFromSaved(
|
||||
req,
|
||||
resp,
|
||||
server_request_callback,
|
||||
7,
|
||||
5,
|
||||
.{
|
||||
// routerTypeMain
|
||||
router_type.server_file_string.get() orelse str: {
|
||||
const name = dev.server_graph.bundled_files.keys()[fromOpaqueFileId(.server, router_type.server_file).get()];
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
const str = try bun.String.createUTF8ForJS(dev.vm.global, dev.relativePath(relative_path_buf, name));
|
||||
router_type.server_file_string = .create(str, dev.vm.global);
|
||||
break :str str;
|
||||
},
|
||||
// routeModules
|
||||
framework_bundle.cached_module_list.get() orelse arr: {
|
||||
bundle.cached_module_list.get() orelse arr: {
|
||||
const global = dev.vm.global;
|
||||
const keys = dev.server_graph.bundled_files.keys();
|
||||
var n: usize = 1;
|
||||
var route = dev.router.routePtr(framework_bundle.route_index);
|
||||
var route = dev.router.routePtr(bundle.route_index);
|
||||
while (true) {
|
||||
if (route.file_layout != .none) n += 1;
|
||||
route = dev.router.routePtr(route.parent.unwrap() orelse break);
|
||||
}
|
||||
const arr = try JSValue.createEmptyArray(global, n);
|
||||
route = dev.router.routePtr(framework_bundle.route_index);
|
||||
route = dev.router.routePtr(bundle.route_index);
|
||||
{
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
var route_name = bun.String.cloneUTF8(dev.relativePath(relative_path_buf, keys[fromOpaqueFileId(.server, route.file_page.unwrap().?).get()]));
|
||||
try arr.putIndex(global, 0, route_name.transferToJS(global));
|
||||
}
|
||||
n = 1;
|
||||
while (true) {
|
||||
if (route.file_layout.unwrap()) |layout| {
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
var layout_name = bun.String.cloneUTF8(dev.relativePath(
|
||||
relative_path_buf,
|
||||
keys[fromOpaqueFileId(.server, layout).get()],
|
||||
@@ -1331,11 +1291,11 @@ fn onFrameworkRequestWithBundle(
|
||||
}
|
||||
route = dev.router.routePtr(route.parent.unwrap() orelse break);
|
||||
}
|
||||
framework_bundle.cached_module_list = .create(arr, global);
|
||||
bundle.cached_module_list = .create(arr, global);
|
||||
break :arr arr;
|
||||
},
|
||||
// clientId
|
||||
framework_bundle.cached_client_bundle_url.get() orelse str: {
|
||||
bundle.cached_client_bundle_url.get() orelse str: {
|
||||
const bundle_index: u32 = route_bundle_index.get();
|
||||
const generation: u32 = route_bundle.client_script_generation;
|
||||
const str = bun.String.createFormat(client_prefix ++ "/route-{}{}.js", .{
|
||||
@@ -1344,21 +1304,17 @@ fn onFrameworkRequestWithBundle(
|
||||
}) catch bun.outOfMemory();
|
||||
defer str.deref();
|
||||
const js = str.toJS(dev.vm.global);
|
||||
framework_bundle.cached_client_bundle_url = .create(js, dev.vm.global);
|
||||
bundle.cached_client_bundle_url = .create(js, dev.vm.global);
|
||||
break :str js;
|
||||
},
|
||||
// styles
|
||||
framework_bundle.cached_css_file_array.get() orelse arr: {
|
||||
bundle.cached_css_file_array.get() orelse arr: {
|
||||
const js = dev.generateCssJSArray(route_bundle) catch bun.outOfMemory();
|
||||
framework_bundle.cached_css_file_array = .create(js, dev.vm.global);
|
||||
bundle.cached_css_file_array = .create(js, dev.vm.global);
|
||||
break :arr js;
|
||||
},
|
||||
// params
|
||||
params_js_value,
|
||||
// setDevServerAsyncLocalStorage function
|
||||
setAsyncLocalStorage,
|
||||
// getDevServerAsyncLocalStorage function
|
||||
getAsyncLocalStorage,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1524,7 +1480,7 @@ fn generateJavaScriptCodeForHTMLFile(
|
||||
|
||||
pub fn onJsRequestWithBundle(dev: *DevServer, bundle_index: RouteBundle.Index, resp: AnyResponse, method: bun.http.Method) void {
|
||||
const route_bundle = dev.routeBundlePtr(bundle_index);
|
||||
const client_bundle = route_bundle.client_bundle orelse generate: {
|
||||
const blob = route_bundle.client_bundle orelse generate: {
|
||||
const payload = dev.generateClientBundle(route_bundle) catch bun.outOfMemory();
|
||||
errdefer dev.allocator.free(payload);
|
||||
route_bundle.client_bundle = StaticRoute.initFromAnyBlob(
|
||||
@@ -1537,7 +1493,7 @@ pub fn onJsRequestWithBundle(dev: *DevServer, bundle_index: RouteBundle.Index, r
|
||||
break :generate route_bundle.client_bundle.?;
|
||||
};
|
||||
dev.source_maps.addWeakRef(route_bundle.sourceMapId());
|
||||
client_bundle.onWithMethod(method, resp);
|
||||
blob.onWithMethod(method, resp);
|
||||
}
|
||||
|
||||
pub fn onSrcRequest(dev: *DevServer, req: *uws.Request, resp: anytype) void {
|
||||
@@ -1587,11 +1543,7 @@ pub const DeferredRequest = struct {
|
||||
pub const List = std.SinglyLinkedList(DeferredRequest);
|
||||
pub const Node = List.Node;
|
||||
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinitImpl, .{
|
||||
.debug_name = "DeferredRequest",
|
||||
});
|
||||
|
||||
const debugLog = bun.Output.Scoped("DlogeferredRequest", .hidden).log;
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinitImpl, .{});
|
||||
|
||||
route_bundle_index: RouteBundle.Index,
|
||||
handler: Handler,
|
||||
@@ -1624,18 +1576,8 @@ pub const DeferredRequest = struct {
|
||||
};
|
||||
};
|
||||
|
||||
fn onAbortWrapper(this: *anyopaque) void {
|
||||
const self: *DeferredRequest = @alignCast(@ptrCast(this));
|
||||
self.onAbortImpl();
|
||||
}
|
||||
|
||||
fn onAbort(this: *DeferredRequest, _: AnyResponse) void {
|
||||
this.onAbortImpl();
|
||||
}
|
||||
|
||||
fn onAbortImpl(this: *DeferredRequest) void {
|
||||
debugLog("DeferredRequest(0x{x}) onAbort", .{@intFromPtr(this)});
|
||||
|
||||
fn onAbort(this: *DeferredRequest, resp: AnyResponse) void {
|
||||
_ = resp;
|
||||
this.abort();
|
||||
assert(this.handler == .aborted);
|
||||
}
|
||||
@@ -1646,8 +1588,7 @@ pub const DeferredRequest = struct {
|
||||
/// such as for bundling failures or aborting the server.
|
||||
/// Does not free the underlying `DeferredRequest.Node`
|
||||
fn deinitImpl(this: *DeferredRequest) void {
|
||||
debugLog("DeferredRequest(0x{x}) deinitImpl", .{@intFromPtr(this)});
|
||||
this.ref_count.assertNoRefs();
|
||||
bun.assert(this.ref_count.active_counts == 0);
|
||||
|
||||
defer this.dev.deferred_request_pool.put(@fieldParentPtr("data", this));
|
||||
switch (this.handler) {
|
||||
@@ -1658,11 +1599,11 @@ pub const DeferredRequest = struct {
|
||||
|
||||
/// Deinitializes state by aborting the connection.
|
||||
fn abort(this: *DeferredRequest) void {
|
||||
debugLog("DeferredRequest(0x{x}) abort", .{@intFromPtr(this)});
|
||||
var handler = this.handler;
|
||||
this.handler = .aborted;
|
||||
switch (handler) {
|
||||
.server_handler => |*saved| {
|
||||
saved.ctx.onAbort(saved.response);
|
||||
saved.js_request.deinit();
|
||||
},
|
||||
.bundled_html_page => |r| {
|
||||
@@ -1709,7 +1650,7 @@ pub fn startAsyncBundle(
|
||||
// Ref server to keep it from closing.
|
||||
if (dev.server) |server| server.onPendingRequest();
|
||||
|
||||
var heap = ThreadLocalArena.init();
|
||||
var heap = try ThreadLocalArena.init();
|
||||
errdefer heap.deinit();
|
||||
const allocator = heap.allocator();
|
||||
const ast_memory_allocator = try allocator.create(bun.ast.ASTMemoryAllocator);
|
||||
@@ -1988,8 +1929,8 @@ fn makeArrayForServerComponentsPatch(dev: *DevServer, global: *jsc.JSGlobalObjec
|
||||
const arr = try jsc.JSArray.createEmpty(global, items.len);
|
||||
const names = dev.server_graph.bundled_files.keys();
|
||||
for (items, 0..) |item, i| {
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
const str = bun.String.cloneUTF8(dev.relativePath(relative_path_buf, names[item.get()]));
|
||||
defer str.deref();
|
||||
try arr.putIndex(global, @intCast(i), str.toJS(global));
|
||||
@@ -2329,70 +2270,14 @@ pub fn finalizeBundle(
|
||||
|
||||
// Load all new chunks into the server runtime.
|
||||
if (!dev.frontend_only and dev.server_graph.current_chunk_len > 0) {
|
||||
// Generate a script_id for server bundles
|
||||
// Use high bit set to distinguish from client bundles, and include generation
|
||||
const server_script_id = SourceMapStore.Key.init((1 << 63) | @as(u64, dev.generation));
|
||||
|
||||
// Get the source map if available and render to JSON
|
||||
var source_map_json = if (dev.server_graph.current_chunk_source_maps.items.len > 0) json: {
|
||||
// Create a temporary source map entry to render
|
||||
var source_map_entry = SourceMapStore.Entry{
|
||||
.ref_count = 1,
|
||||
.paths = &.{},
|
||||
.files = .empty,
|
||||
.overlapping_memory_cost = 0,
|
||||
};
|
||||
|
||||
// Fill the source map entry
|
||||
var arena = std.heap.ArenaAllocator.init(dev.allocator);
|
||||
defer arena.deinit();
|
||||
try dev.server_graph.takeSourceMap(arena.allocator(), dev.allocator, &source_map_entry);
|
||||
defer {
|
||||
source_map_entry.ref_count = 0;
|
||||
source_map_entry.deinit(dev);
|
||||
}
|
||||
|
||||
const json_data = try source_map_entry.renderJSON(
|
||||
dev,
|
||||
arena.allocator(),
|
||||
.hmr_chunk,
|
||||
dev.allocator,
|
||||
.server,
|
||||
);
|
||||
break :json json_data;
|
||||
} else null;
|
||||
defer if (source_map_json) |json| bun.default_allocator.free(json);
|
||||
|
||||
const server_bundle = try dev.server_graph.takeJSBundle(&.{
|
||||
.kind = .hmr_chunk,
|
||||
.script_id = server_script_id,
|
||||
});
|
||||
const server_bundle = try dev.server_graph.takeJSBundle(&.{ .kind = .hmr_chunk });
|
||||
defer dev.allocator.free(server_bundle);
|
||||
|
||||
// TODO: is this the best place to set this? Would it be better to
|
||||
// transpile the server modules to replace `new Response(...)` with `new
|
||||
// ResponseBake(...)`??
|
||||
dev.vm.setAllowJSXInResponseConstructor(true);
|
||||
|
||||
const server_modules = if (bun.take(&source_map_json)) |json| blk: {
|
||||
// This memory will be owned by the `DevServerSourceProvider` in C++
|
||||
// from here on out
|
||||
dev.allocation_scope.leakSlice(json);
|
||||
|
||||
break :blk c.BakeLoadServerHmrPatchWithSourceMap(
|
||||
@ptrCast(dev.vm.global),
|
||||
bun.String.cloneUTF8(server_bundle),
|
||||
json.ptr,
|
||||
json.len,
|
||||
) catch |err| {
|
||||
// No user code has been evaluated yet, since everything is to
|
||||
// be wrapped in a function clousure. This means that the likely
|
||||
// error is going to be a syntax error, or other mistake in the
|
||||
// bundler.
|
||||
dev.vm.printErrorLikeObjectToConsole(dev.vm.global.takeException(err));
|
||||
@panic("Error thrown while evaluating server code. This is always a bug in the bundler.");
|
||||
};
|
||||
} else c.BakeLoadServerHmrPatch(@ptrCast(dev.vm.global), bun.String.cloneLatin1(server_bundle)) catch |err| {
|
||||
const server_modules = c.BakeLoadServerHmrPatch(@ptrCast(dev.vm.global), bun.String.cloneLatin1(server_bundle)) catch |err| {
|
||||
// No user code has been evaluated yet, since everything is to
|
||||
// be wrapped in a function clousure. This means that the likely
|
||||
// error is going to be a syntax error, or other mistake in the
|
||||
// bundler.
|
||||
dev.vm.printErrorLikeObjectToConsole(dev.vm.global.takeException(err));
|
||||
@panic("Error thrown while evaluating server code. This is always a bug in the bundler.");
|
||||
};
|
||||
@@ -2718,8 +2603,8 @@ pub fn finalizeBundle(
|
||||
// Intentionally creating a new scope here so we can limit the lifetime
|
||||
// of the `relative_path_buf`
|
||||
{
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
|
||||
// Compute a file name to display
|
||||
const file_name: ?[]const u8 = if (current_bundle.had_reload_event)
|
||||
@@ -2938,7 +2823,7 @@ fn onRequest(dev: *DevServer, req: *Request, resp: anytype) void {
|
||||
dev.ensureRouteIsBundled(
|
||||
dev.getOrPutRouteBundle(.{ .framework = route_index }) catch bun.outOfMemory(),
|
||||
.server_handler,
|
||||
.{ .req = req },
|
||||
req,
|
||||
AnyResponse.init(resp),
|
||||
) catch bun.outOfMemory();
|
||||
return;
|
||||
@@ -2953,31 +2838,7 @@ fn onRequest(dev: *DevServer, req: *Request, resp: anytype) void {
|
||||
}
|
||||
|
||||
pub fn respondForHTMLBundle(dev: *DevServer, html: *HTMLBundle.HTMLBundleRoute, req: *uws.Request, resp: AnyResponse) !void {
|
||||
try dev.ensureRouteIsBundled(try dev.getOrPutRouteBundle(.{ .html = html }), .bundled_html_page, .{ .req = req }, resp);
|
||||
}
|
||||
|
||||
// TODO: path params
|
||||
pub fn handleRenderRedirect(
|
||||
dev: *DevServer,
|
||||
saved_request: bun.jsc.API.SavedRequest,
|
||||
render_path: []const u8,
|
||||
resp: AnyResponse,
|
||||
) !void {
|
||||
// Match the render path against the router
|
||||
var params: FrameworkRouter.MatchedParams = undefined;
|
||||
if (dev.router.matchSlow(render_path, ¶ms)) |route_index| {
|
||||
// Found a matching route, bundle it and handle the request
|
||||
dev.ensureRouteIsBundled(
|
||||
dev.getOrPutRouteBundle(.{ .framework = route_index }) catch bun.outOfMemory(),
|
||||
.server_handler,
|
||||
.{ .saved = saved_request },
|
||||
resp,
|
||||
) catch bun.outOfMemory();
|
||||
return;
|
||||
}
|
||||
|
||||
// No matching route found - render 404
|
||||
sendBuiltInNotFound(resp);
|
||||
try dev.ensureRouteIsBundled(try dev.getOrPutRouteBundle(.{ .html = html }), .bundled_html_page, req, resp);
|
||||
}
|
||||
|
||||
fn getOrPutRouteBundle(dev: *DevServer, route: RouteBundle.UnresolvedIndex) !RouteBundle.Index {
|
||||
@@ -3046,18 +2907,22 @@ fn encodeSerializedFailures(
|
||||
buf: *std.ArrayList(u8),
|
||||
inspector_agent: ?*BunFrontendDevServerAgent,
|
||||
) bun.OOM!void {
|
||||
var all_failures_len: usize = 0;
|
||||
for (failures) |fail| all_failures_len += fail.data.len;
|
||||
var all_failures = try std.ArrayListUnmanaged(u8).initCapacity(dev.allocator, all_failures_len);
|
||||
defer all_failures.deinit(dev.allocator);
|
||||
for (failures) |fail| all_failures.appendSliceAssumeCapacity(fail.data);
|
||||
|
||||
const failures_start_buf_pos = buf.items.len;
|
||||
for (failures) |fail| {
|
||||
const len = bun.base64.encodeLen(fail.data);
|
||||
|
||||
const len = bun.base64.encodeLen(all_failures.items);
|
||||
try buf.ensureUnusedCapacity(len);
|
||||
const to_write_into = buf.unusedCapacitySlice();
|
||||
buf.items.len += bun.base64.encode(to_write_into, all_failures.items);
|
||||
try buf.ensureUnusedCapacity(len);
|
||||
const start = buf.items.len;
|
||||
buf.items.len += len;
|
||||
const to_write_into = buf.items[start..];
|
||||
|
||||
var encoded = to_write_into[0..bun.base64.encode(to_write_into, fail.data)];
|
||||
while (encoded.len > 0 and encoded[encoded.len - 1] == '=') {
|
||||
encoded.len -= 1;
|
||||
}
|
||||
|
||||
buf.items.len = start + encoded.len;
|
||||
}
|
||||
|
||||
// Re-use the encoded buffer to avoid encoding failures more times than neccecary.
|
||||
if (inspector_agent) |agent| {
|
||||
@@ -3470,8 +3335,8 @@ pub fn writeVisualizerMessage(dev: *DevServer, payload: *std.ArrayList(u8)) !voi
|
||||
g.bundled_files.values(),
|
||||
0..,
|
||||
) |k, v, i| {
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
const normalized_key = dev.relativePath(relative_path_buf, k);
|
||||
try w.writeInt(u32, @intCast(normalized_key.len), .little);
|
||||
if (k.len == 0) continue;
|
||||
@@ -3734,11 +3599,6 @@ const c = struct {
|
||||
return bun.jsc.fromJSHostCall(global, @src(), f, .{ global, code });
|
||||
}
|
||||
|
||||
fn BakeLoadServerHmrPatchWithSourceMap(global: *jsc.JSGlobalObject, code: bun.String, source_map_json_ptr: [*]const u8, source_map_json_len: usize) bun.JSError!JSValue {
|
||||
const f = @extern(*const fn (*jsc.JSGlobalObject, bun.String, [*]const u8, usize) callconv(.c) JSValue, .{ .name = "BakeLoadServerHmrPatchWithSourceMap" }).*;
|
||||
return bun.jsc.fromJSHostCall(global, @src(), f, .{ global, code, source_map_json_ptr, source_map_json_len });
|
||||
}
|
||||
|
||||
fn BakeLoadInitialServerCode(global: *jsc.JSGlobalObject, code: bun.String, separate_ssr_graph: bool) bun.JSError!JSValue {
|
||||
const f = @extern(*const fn (*jsc.JSGlobalObject, bun.String, bool) callconv(.c) JSValue, .{ .name = "BakeLoadInitialServerCode" }).*;
|
||||
return bun.jsc.fromJSHostCall(global, @src(), f, .{ global, code, separate_ssr_graph });
|
||||
@@ -3908,8 +3768,8 @@ pub fn onRouterCollisionError(dev: *DevServer, rel_path: []const u8, other_id: O
|
||||
},
|
||||
});
|
||||
Output.prettyErrorln(" - <blue>{s}<r>", .{rel_path});
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
Output.prettyErrorln(" - <blue>{s}<r>", .{
|
||||
dev.relativePath(relative_path_buf, dev.server_graph.bundled_files.keys()[fromOpaqueFileId(.server, other_id).get()]),
|
||||
});
|
||||
@@ -3937,9 +3797,16 @@ fn fromOpaqueFileId(comptime side: bake.Side, id: OpaqueFileId) IncrementalGraph
|
||||
}
|
||||
|
||||
/// Returns posix style path, suitible for URLs and reproducible hashes.
|
||||
/// Calculate the relative path from the dev server root.
|
||||
/// The caller must provide a PathBuffer from the pool.
|
||||
/// To avoid overwriting memory, this has a lock for the buffer.
|
||||
///
|
||||
///
|
||||
/// You must pass the pathbuffer contained within `dev.relative_path_buffer`!
|
||||
pub fn relativePath(dev: *DevServer, relative_path_buf: *bun.PathBuffer, path: []const u8) []const u8 {
|
||||
// You must pass the pathbuffer contained within `dev.relative_path_buffer`!
|
||||
bun.assert_eql(
|
||||
@intFromPtr(relative_path_buf),
|
||||
@intFromPtr(&dev.relative_path_buf.unsynchronized_value),
|
||||
);
|
||||
bun.assert(dev.root[dev.root.len - 1] != '/');
|
||||
|
||||
if (!std.fs.path.isAbsolute(path)) {
|
||||
@@ -4209,6 +4076,7 @@ const SourceMap = bun.sourcemap;
|
||||
const Watcher = bun.Watcher;
|
||||
const assert = bun.assert;
|
||||
const bake = bun.bake;
|
||||
const DebugGuardedValue = bun.threading.DebugGuardedValue;
|
||||
const DynamicBitSetUnmanaged = bun.bit_set.DynamicBitSetUnmanaged;
|
||||
const Log = bun.logger.Log;
|
||||
const MimeType = bun.http.MimeType;
|
||||
|
||||
@@ -124,21 +124,9 @@ pub fn runWithBody(ctx: *ErrorReportRequest, body: []const u8, r: AnyResponse) !
|
||||
}
|
||||
const result: *const SourceMapStore.GetResult = &(gop.value_ptr.* orelse continue);
|
||||
|
||||
// When before the first generated line, remap to the HMR runtime.
|
||||
//
|
||||
// Reminder that the HMR runtime is *not* sourcemapped. And appears
|
||||
// first in the bundle. This means that the mappings usually looks like
|
||||
// this:
|
||||
//
|
||||
// AAAA;;;;;;;;;;;ICGA,qCAA4B;
|
||||
// ^ ^ generated_mappings[1], actual code
|
||||
// ^
|
||||
// ^ generated_mappings[0], we always start it with this
|
||||
//
|
||||
// So we can know if the frame is inside the HMR runtime if
|
||||
// `frame.position.line < generated_mappings[1].lines`.
|
||||
// When before the first generated line, remap to the HMR runtime
|
||||
const generated_mappings = result.mappings.generated();
|
||||
if (generated_mappings.len <= 1 or frame.position.line.zeroBased() < generated_mappings[1].lines.zeroBased()) {
|
||||
if (frame.position.line.oneBased() < generated_mappings[1].lines) {
|
||||
frame.source_url = .init(runtime_name); // matches value in source map
|
||||
frame.position = .invalid;
|
||||
continue;
|
||||
@@ -159,12 +147,12 @@ pub fn runWithBody(ctx: *ErrorReportRequest, body: []const u8, r: AnyResponse) !
|
||||
if (index >= 1 and (index - 1) < result.file_paths.len) {
|
||||
const abs_path = result.file_paths[@intCast(index - 1)];
|
||||
frame.source_url = .init(abs_path);
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = ctx.dev.relative_path_buf.lock();
|
||||
const rel_path = ctx.dev.relativePath(relative_path_buf, abs_path);
|
||||
if (bun.strings.eql(frame.function_name.value.ZigString.slice(), rel_path)) {
|
||||
frame.function_name = .empty;
|
||||
}
|
||||
ctx.dev.relative_path_buf.unlock();
|
||||
frame.remapped = true;
|
||||
|
||||
if (runtime_lines == null) {
|
||||
@@ -253,8 +241,7 @@ pub fn runWithBody(ctx: *ErrorReportRequest, body: []const u8, r: AnyResponse) !
|
||||
|
||||
const src_to_write = frame.source_url.value.ZigString.slice();
|
||||
if (bun.strings.hasPrefixComptime(src_to_write, "/")) {
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = ctx.dev.relative_path_buf.lock();
|
||||
const file = ctx.dev.relativePath(relative_path_buf, src_to_write);
|
||||
try w.writeInt(u32, @intCast(file.len), .little);
|
||||
try w.writeAll(file);
|
||||
|
||||
@@ -76,12 +76,6 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
.server => void,
|
||||
},
|
||||
|
||||
/// Source maps for server chunks
|
||||
current_chunk_source_maps: if (side == .server) ArrayListUnmanaged(PackedMap.RefOrEmpty) else void = if (side == .server) .empty,
|
||||
|
||||
/// File indices for server chunks to track which file each chunk comes from
|
||||
current_chunk_file_indices: if (side == .server) ArrayListUnmanaged(FileIndex) else void = if (side == .server) .empty,
|
||||
|
||||
pub const empty: @This() = .{
|
||||
.bundled_files = .empty,
|
||||
.stale_files = .empty,
|
||||
@@ -95,8 +89,6 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
.current_chunk_parts = .empty,
|
||||
|
||||
.current_css_files = if (side == .client) .empty,
|
||||
.current_chunk_source_maps = if (side == .server) .empty else {},
|
||||
.current_chunk_file_indices = if (side == .server) .empty else {},
|
||||
};
|
||||
|
||||
pub const File = switch (side) {
|
||||
@@ -190,7 +182,7 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
};
|
||||
|
||||
comptime {
|
||||
if (!Environment.ci_assert) {
|
||||
if (@import("builtin").mode == .ReleaseFast or @import("builtin").mode == .ReleaseSmall) {
|
||||
bun.assert_eql(@sizeOf(@This()), @sizeOf(u64) * 5);
|
||||
bun.assert_eql(@alignOf(@This()), @alignOf([*]u8));
|
||||
}
|
||||
@@ -332,13 +324,6 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
.current_chunk_len = {},
|
||||
.current_chunk_parts = g.current_chunk_parts.deinit(allocator),
|
||||
.current_css_files = if (side == .client) g.current_css_files.deinit(allocator),
|
||||
.current_chunk_source_maps = if (side == .server) {
|
||||
for (g.current_chunk_source_maps.items) |source_map| {
|
||||
source_map.deref(&g.owner().*);
|
||||
}
|
||||
g.current_chunk_source_maps.deinit(allocator);
|
||||
},
|
||||
.current_chunk_file_indices = if (side == .server) g.current_chunk_file_indices.deinit(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -371,14 +356,6 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
.empty => {},
|
||||
}
|
||||
}
|
||||
} else if (side == .server) {
|
||||
graph += DevServer.memoryCostArrayList(g.current_chunk_source_maps);
|
||||
graph += DevServer.memoryCostArrayList(g.current_chunk_file_indices);
|
||||
for (g.current_chunk_source_maps.items) |source_map| {
|
||||
if (source_map == .ref) {
|
||||
source_maps += source_map.ref.data.memoryCostWithDedupe(new_dedupe_bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
return .{
|
||||
.graph = graph,
|
||||
@@ -511,7 +488,7 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
.js => |js| {
|
||||
// Insert new source map or patch existing empty source map.
|
||||
const source_map: PackedMap.RefOrEmpty = brk: {
|
||||
if (js.source_map) |*source_map| {
|
||||
if (js.source_map) |source_map| {
|
||||
bun.debugAssert(!flags.is_html_route); // suspect behind #17956
|
||||
if (source_map.chunk.buffer.len() > 0) {
|
||||
flags.source_map_state = .ref;
|
||||
@@ -608,40 +585,12 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
if (content == .js) {
|
||||
try g.current_chunk_parts.append(dev.allocator, content.js.code);
|
||||
g.current_chunk_len += content.js.code.len;
|
||||
|
||||
// Track the file index for this chunk
|
||||
try g.current_chunk_file_indices.append(dev.allocator, file_index);
|
||||
|
||||
// TODO: we probably want to store SSR chunks but not
|
||||
// server chunks, but not 100% sure
|
||||
const should_immediately_free_sourcemap = false;
|
||||
if (should_immediately_free_sourcemap) {
|
||||
if (content.js.source_map) |source_map| {
|
||||
var take = source_map.chunk.buffer;
|
||||
take.deinit();
|
||||
if (source_map.escaped_source) |escaped_source| {
|
||||
bun.default_allocator.free(escaped_source);
|
||||
}
|
||||
if (content.js.source_map) |source_map| {
|
||||
var take = source_map.chunk.buffer;
|
||||
take.deinit();
|
||||
if (source_map.escaped_source) |escaped_source| {
|
||||
bun.default_allocator.free(escaped_source);
|
||||
}
|
||||
} else {
|
||||
if (content.js.source_map) |source_map| append_empty: {
|
||||
const packed_map = PackedMap.newNonEmpty(source_map.chunk, source_map.escaped_source orelse break :append_empty);
|
||||
try g.current_chunk_source_maps.append(dev.allocator, .{
|
||||
.ref = packed_map,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Must precompute this. Otherwise, source maps won't have
|
||||
// the info needed to concatenate VLQ mappings.
|
||||
const count: u32 = @intCast(bun.strings.countChar(content.js.code, '\n'));
|
||||
try g.current_chunk_source_maps.append(dev.allocator, PackedMap.RefOrEmpty{
|
||||
.empty = .{
|
||||
.line_count = .init(count),
|
||||
// TODO: not sure if this is correct
|
||||
.html_bundle_route_index = .none,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -665,7 +614,7 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
bundle_graph_index: bun.ast.Index,
|
||||
temp_alloc: Allocator,
|
||||
) bun.OOM!void {
|
||||
const log = bun.Output.scoped(.processChunkDependencies, .visible);
|
||||
const log = bun.Output.scoped(.processChunkDependencies, false);
|
||||
const file_index: FileIndex = ctx.getCachedIndex(side, bundle_graph_index).*.unwrap() orelse
|
||||
@panic("unresolved index"); // do not process for failed chunks
|
||||
log("index id={d} {}:", .{
|
||||
@@ -766,7 +715,7 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
fn disconnectEdgeFromDependencyList(g: *@This(), edge_index: EdgeIndex) void {
|
||||
const edge = &g.edges.items[edge_index.get()];
|
||||
const imported = edge.imported.get();
|
||||
const log = bun.Output.scoped(.disconnectEdgeFromDependencyList, .hidden);
|
||||
const log = bun.Output.scoped(.disconnectEdgeFromDependencyList, true);
|
||||
log("detach edge={d} | id={d} {} -> id={d} {} (first_dep={d})", .{
|
||||
edge_index.get(),
|
||||
edge.dependency.get(),
|
||||
@@ -855,7 +804,7 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
css,
|
||||
},
|
||||
) bun.OOM!enum { @"continue", stop } {
|
||||
const log = bun.Output.scoped(.processEdgeAttachment, .visible);
|
||||
const log = bun.Output.scoped(.processEdgeAttachment, false);
|
||||
|
||||
// When an import record is duplicated, it gets marked unused.
|
||||
// This happens in `ConvertESMExportsForHmr.deduplicatedImport`
|
||||
@@ -974,7 +923,7 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
// don't call this function for CSS sources
|
||||
bun.assert(ctx.loaders[index.get()] != .css);
|
||||
|
||||
const log = bun.Output.scoped(.processChunkDependencies, .visible);
|
||||
const log = bun.Output.scoped(.processChunkDependencies, false);
|
||||
for (ctx.import_records[index.get()].slice()) |import_record| {
|
||||
// When an import record is duplicated, it gets marked unused.
|
||||
// This happens in `ConvertESMExportsForHmr.deduplicatedImport`
|
||||
@@ -1464,8 +1413,8 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
// the error list as it changes while also supporting a REPL
|
||||
log.print(Output.errorWriter()) catch {};
|
||||
const failure = failure: {
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = dev.relative_path_buf.lock();
|
||||
defer dev.relative_path_buf.unlock();
|
||||
// this string is just going to be memcpy'd into the log buffer
|
||||
const owner_display_name = dev.relativePath(relative_path_buf, gop.key_ptr.*);
|
||||
break :failure try SerializedFailure.initFromLog(
|
||||
@@ -1628,12 +1577,7 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
g.owner().graph_safety_lock.assertLocked();
|
||||
g.current_chunk_len = 0;
|
||||
g.current_chunk_parts.clearRetainingCapacity();
|
||||
if (side == .client) {
|
||||
g.current_css_files.clearRetainingCapacity();
|
||||
} else if (side == .server) {
|
||||
g.current_chunk_source_maps.clearRetainingCapacity();
|
||||
g.current_chunk_file_indices.clearRetainingCapacity();
|
||||
}
|
||||
if (side == .client) g.current_css_files.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
const TakeJSBundleOptions = switch (side) {
|
||||
@@ -1646,7 +1590,6 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
},
|
||||
.server => struct {
|
||||
kind: ChunkKind,
|
||||
script_id: SourceMapStore.Key,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1694,8 +1637,8 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
try w.writeAll("}, {\n main: ");
|
||||
const initial_response_entry_point = options.initial_response_entry_point;
|
||||
if (initial_response_entry_point.len > 0) {
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = g.owner().relative_path_buf.lock();
|
||||
defer g.owner().relative_path_buf.unlock();
|
||||
try bun.js_printer.writeJSONString(
|
||||
g.owner().relativePath(relative_path_buf, initial_response_entry_point),
|
||||
@TypeOf(w),
|
||||
@@ -1720,8 +1663,8 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
|
||||
if (options.react_refresh_entry_point.len > 0) {
|
||||
try w.writeAll(",\n refresh: ");
|
||||
const relative_path_buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(relative_path_buf);
|
||||
const relative_path_buf = g.owner().relative_path_buf.lock();
|
||||
defer g.owner().relative_path_buf.unlock();
|
||||
try bun.js_printer.writeJSONString(
|
||||
g.owner().relativePath(relative_path_buf, options.react_refresh_entry_point),
|
||||
@TypeOf(w),
|
||||
@@ -1789,70 +1732,44 @@ pub fn IncrementalGraph(side: bake.Side) type {
|
||||
};
|
||||
|
||||
/// Uses `arena` as a temporary allocator, fills in all fields of `out` except ref_count
|
||||
pub fn takeSourceMap(g: *@This(), _: std.mem.Allocator, gpa: Allocator, out: *SourceMapStore.Entry) bun.OOM!void {
|
||||
pub fn takeSourceMap(g: *@This(), arena: std.mem.Allocator, gpa: Allocator, out: *SourceMapStore.Entry) bun.OOM!void {
|
||||
if (side == .server) @compileError("not implemented");
|
||||
|
||||
const paths = g.bundled_files.keys();
|
||||
const files = g.bundled_files.values();
|
||||
|
||||
switch (side) {
|
||||
.client => {
|
||||
const files = g.bundled_files.values();
|
||||
// This buffer is temporary, holding the quoted source paths, joined with commas.
|
||||
var source_map_strings = std.ArrayList(u8).init(arena);
|
||||
defer source_map_strings.deinit();
|
||||
|
||||
const buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(buf);
|
||||
const buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(buf);
|
||||
|
||||
var file_paths = try ArrayListUnmanaged([]const u8).initCapacity(gpa, g.current_chunk_parts.items.len);
|
||||
errdefer file_paths.deinit(gpa);
|
||||
var contained_maps: bun.MultiArrayList(PackedMap.RefOrEmpty) = .empty;
|
||||
try contained_maps.ensureTotalCapacity(gpa, g.current_chunk_parts.items.len);
|
||||
errdefer contained_maps.deinit(gpa);
|
||||
var file_paths = try ArrayListUnmanaged([]const u8).initCapacity(gpa, g.current_chunk_parts.items.len);
|
||||
errdefer file_paths.deinit(gpa);
|
||||
var contained_maps: bun.MultiArrayList(PackedMap.RefOrEmpty) = .empty;
|
||||
try contained_maps.ensureTotalCapacity(gpa, g.current_chunk_parts.items.len);
|
||||
errdefer contained_maps.deinit(gpa);
|
||||
|
||||
var overlapping_memory_cost: u32 = 0;
|
||||
var overlapping_memory_cost: u32 = 0;
|
||||
|
||||
for (g.current_chunk_parts.items) |file_index| {
|
||||
file_paths.appendAssumeCapacity(paths[file_index.get()]);
|
||||
const source_map = files[file_index.get()].sourceMap();
|
||||
contained_maps.appendAssumeCapacity(source_map.dupeRef());
|
||||
if (source_map == .ref) {
|
||||
overlapping_memory_cost += @intCast(source_map.ref.data.memoryCost());
|
||||
}
|
||||
}
|
||||
|
||||
overlapping_memory_cost += @intCast(contained_maps.memoryCost() + DevServer.memoryCostSlice(file_paths.items));
|
||||
|
||||
out.* = .{
|
||||
.ref_count = out.ref_count,
|
||||
.paths = file_paths.items,
|
||||
.files = contained_maps,
|
||||
.overlapping_memory_cost = overlapping_memory_cost,
|
||||
};
|
||||
},
|
||||
.server => {
|
||||
var file_paths = try ArrayListUnmanaged([]const u8).initCapacity(gpa, g.current_chunk_parts.items.len);
|
||||
errdefer file_paths.deinit(gpa);
|
||||
var contained_maps: bun.MultiArrayList(PackedMap.RefOrEmpty) = .empty;
|
||||
try contained_maps.ensureTotalCapacity(gpa, g.current_chunk_parts.items.len);
|
||||
errdefer contained_maps.deinit(gpa);
|
||||
|
||||
var overlapping_memory_cost: u32 = 0;
|
||||
|
||||
// For server, we use the tracked file indices to get the correct paths
|
||||
for (g.current_chunk_file_indices.items, g.current_chunk_source_maps.items) |file_index, source_map| {
|
||||
file_paths.appendAssumeCapacity(paths[file_index.get()]);
|
||||
contained_maps.appendAssumeCapacity(source_map.dupeRef());
|
||||
if (source_map == .ref) {
|
||||
overlapping_memory_cost += @intCast(source_map.ref.data.memoryCost());
|
||||
}
|
||||
}
|
||||
|
||||
overlapping_memory_cost += @intCast(contained_maps.memoryCost() + DevServer.memoryCostSlice(file_paths.items));
|
||||
|
||||
out.* = .{
|
||||
.ref_count = out.ref_count,
|
||||
.paths = file_paths.items,
|
||||
.files = contained_maps,
|
||||
.overlapping_memory_cost = overlapping_memory_cost,
|
||||
};
|
||||
},
|
||||
for (g.current_chunk_parts.items) |file_index| {
|
||||
file_paths.appendAssumeCapacity(paths[file_index.get()]);
|
||||
const source_map = files[file_index.get()].sourceMap();
|
||||
contained_maps.appendAssumeCapacity(source_map.dupeRef());
|
||||
if (source_map == .ref) {
|
||||
overlapping_memory_cost += @intCast(source_map.ref.data.memoryCost());
|
||||
}
|
||||
}
|
||||
|
||||
overlapping_memory_cost += @intCast(contained_maps.memoryCost() + DevServer.memoryCostSlice(file_paths.items));
|
||||
|
||||
out.* = .{
|
||||
.ref_count = out.ref_count,
|
||||
.paths = file_paths.items,
|
||||
.files = contained_maps,
|
||||
.overlapping_memory_cost = overlapping_memory_cost,
|
||||
};
|
||||
}
|
||||
|
||||
fn disconnectAndDeleteFile(g: *@This(), file_index: FileIndex) void {
|
||||
|
||||
@@ -80,7 +80,7 @@ pub fn quotedContents(self: *const @This()) []u8 {
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (!Environment.ci_assert) {
|
||||
if (!Environment.isDebug) {
|
||||
assert_eql(@sizeOf(@This()), @sizeOf(usize) * 7);
|
||||
assert_eql(@alignOf(@This()), @alignOf(usize));
|
||||
}
|
||||
|
||||
@@ -75,11 +75,11 @@ pub const Entry = struct {
|
||||
pub fn renderMappings(map: Entry, kind: ChunkKind, arena: Allocator, gpa: Allocator) ![]u8 {
|
||||
var j: StringJoiner = .{ .allocator = arena };
|
||||
j.pushStatic("AAAA");
|
||||
try joinVLQ(&map, kind, &j, arena, .client);
|
||||
try joinVLQ(&map, kind, &j, arena);
|
||||
return j.done(gpa);
|
||||
}
|
||||
|
||||
pub fn renderJSON(map: *const Entry, dev: *DevServer, arena: Allocator, kind: ChunkKind, gpa: Allocator, side: bake.Side) ![]u8 {
|
||||
pub fn renderJSON(map: *const Entry, dev: *DevServer, arena: Allocator, kind: ChunkKind, gpa: Allocator) ![]u8 {
|
||||
const map_files = map.files.slice();
|
||||
const paths = map.paths;
|
||||
|
||||
@@ -105,22 +105,13 @@ pub const Entry = struct {
|
||||
|
||||
if (std.fs.path.isAbsolute(path)) {
|
||||
const is_windows_drive_path = Environment.isWindows and path[0] != '/';
|
||||
|
||||
// On the client we prefix the sourcemap path with "file://" and
|
||||
// percent encode it
|
||||
if (side == .client) {
|
||||
try source_map_strings.appendSlice(if (is_windows_drive_path)
|
||||
"\"file:///"
|
||||
else
|
||||
"\"file://");
|
||||
} else {
|
||||
try source_map_strings.append('"');
|
||||
}
|
||||
|
||||
try source_map_strings.appendSlice(if (is_windows_drive_path)
|
||||
"\"file:///"
|
||||
else
|
||||
"\"file://");
|
||||
if (Environment.isWindows and !is_windows_drive_path) {
|
||||
// UNC namespace -> file://server/share/path.ext
|
||||
encodeSourceMapPath(
|
||||
side,
|
||||
bun.strings.percentEncodeWrite(
|
||||
if (path.len > 2 and path[0] == '/' and path[1] == '/')
|
||||
path[2..]
|
||||
else
|
||||
@@ -135,7 +126,7 @@ pub const Entry = struct {
|
||||
// -> file:///path/to/file.js
|
||||
// windows drive letter paths have the extra slash added
|
||||
// -> file:///C:/path/to/file.js
|
||||
encodeSourceMapPath(side, path, &source_map_strings) catch |err| switch (err) {
|
||||
bun.strings.percentEncodeWrite(path, &source_map_strings) catch |err| switch (err) {
|
||||
error.IncompleteUTF8 => @panic("Unexpected: asset with incomplete UTF-8 as file path"),
|
||||
error.OutOfMemory => |e| return e,
|
||||
};
|
||||
@@ -183,14 +174,14 @@ pub const Entry = struct {
|
||||
j.pushStatic(
|
||||
\\],"names":[],"mappings":"AAAA
|
||||
);
|
||||
try joinVLQ(map, kind, &j, arena, side);
|
||||
try joinVLQ(map, kind, &j, arena);
|
||||
|
||||
const json_bytes = try j.doneWithEnd(gpa, "\"}");
|
||||
errdefer @compileError("last try should be the final alloc");
|
||||
|
||||
if (bun.FeatureFlags.bake_debugging_features) if (dev.dump_dir) |dump_dir| {
|
||||
const rel_path_escaped = if (side == .client) "latest_chunk.js.map" else "latest_hmr.js.map";
|
||||
dumpBundle(dump_dir, if (side == .client) .client else .server, rel_path_escaped, json_bytes, false) catch |err| {
|
||||
const rel_path_escaped = "latest_chunk.js.map";
|
||||
dumpBundle(dump_dir, .client, rel_path_escaped, json_bytes, false) catch |err| {
|
||||
bun.handleErrorReturnTrace(err, @errorReturnTrace());
|
||||
Output.warn("Could not dump bundle: {}", .{err});
|
||||
};
|
||||
@@ -199,23 +190,14 @@ pub const Entry = struct {
|
||||
return json_bytes;
|
||||
}
|
||||
|
||||
fn encodeSourceMapPath(
|
||||
side: bake.Side,
|
||||
utf8_input: []const u8,
|
||||
writer: *std.ArrayList(u8),
|
||||
) error{ OutOfMemory, IncompleteUTF8 }!void {
|
||||
// On the client, percent encode everything so it works in the browser
|
||||
if (side == .client) {
|
||||
return bun.strings.percentEncodeWrite(utf8_input, writer);
|
||||
}
|
||||
|
||||
// On the server, we don't need to do anything
|
||||
try writer.appendSlice(utf8_input);
|
||||
}
|
||||
|
||||
fn joinVLQ(map: *const Entry, kind: ChunkKind, j: *StringJoiner, arena: Allocator, side: bake.Side) !void {
|
||||
fn joinVLQ(map: *const Entry, kind: ChunkKind, j: *StringJoiner, arena: Allocator) !void {
|
||||
const map_files = map.files.slice();
|
||||
|
||||
const runtime: bake.HmrRuntime = switch (kind) {
|
||||
.initial_response => bun.bake.getHmrRuntime(.client),
|
||||
.hmr_chunk => comptime .init("self[Symbol.for(\"bun:hmr\")]({\n"),
|
||||
};
|
||||
|
||||
var prev_end_state: SourceMap.SourceMapState = .{
|
||||
.generated_line = 0,
|
||||
.generated_column = 0,
|
||||
@@ -224,20 +206,8 @@ pub const Entry = struct {
|
||||
.original_column = 0,
|
||||
};
|
||||
|
||||
var lines_between: u32 = lines_between: {
|
||||
if (side == .client) {
|
||||
const runtime: bake.HmrRuntime = switch (kind) {
|
||||
.initial_response => bun.bake.getHmrRuntime(.client),
|
||||
.hmr_chunk => comptime .init("self[Symbol.for(\"bun:hmr\")]({\n"),
|
||||
};
|
||||
// +2 because the magic fairy in my dreams said it would align the source maps.
|
||||
// TODO: why the fuck is this 2?
|
||||
const lines_between: u32 = runtime.line_count + 2;
|
||||
break :lines_between lines_between;
|
||||
}
|
||||
|
||||
break :lines_between 0;
|
||||
};
|
||||
// +2 because the magic fairy in my dreams said it would align the source maps.
|
||||
var lines_between: u32 = runtime.line_count + 2;
|
||||
|
||||
// Join all of the mappings together.
|
||||
for (map_files.items(.tags), map_files.items(.data), 1..) |tag, chunk, source_index| switch (tag) {
|
||||
@@ -253,7 +223,7 @@ pub const Entry = struct {
|
||||
continue;
|
||||
},
|
||||
.ref => {
|
||||
const content: *PackedMap = chunk.ref.data;
|
||||
const content = chunk.ref.data;
|
||||
const start_state: SourceMap.SourceMapState = .{
|
||||
.source_index = @intCast(source_index),
|
||||
.generated_line = @intCast(lines_between),
|
||||
|
||||
@@ -48,6 +48,7 @@ pub fn memoryCostDetailed(dev: *DevServer) MemoryCost {
|
||||
.server_register_update_callback = {},
|
||||
.server_fetch_function_callback = {},
|
||||
.watcher_atomics = {},
|
||||
.relative_path_buf = {},
|
||||
|
||||
// pointers that are not considered a part of DevServer
|
||||
.vm = {},
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "DevServerSourceProvider.h"
|
||||
#include "BunBuiltinNames.h"
|
||||
#include "BunString.h"
|
||||
|
||||
// The Zig implementation will be provided to handle registration
|
||||
extern "C" void Bun__addDevServerSourceProvider(void* bun_vm, Bake::DevServerSourceProvider* opaque_source_provider, BunString* specifier);
|
||||
|
||||
// Export functions for Zig to access DevServerSourceProvider
|
||||
extern "C" BunString DevServerSourceProvider__getSourceSlice(Bake::DevServerSourceProvider* provider)
|
||||
{
|
||||
return Bun::toStringView(provider->source());
|
||||
}
|
||||
|
||||
extern "C" Bake::SourceMapData DevServerSourceProvider__getSourceMapJSON(Bake::DevServerSourceProvider* provider)
|
||||
{
|
||||
return provider->sourceMapJSON();
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include "headers-handwritten.h"
|
||||
#include "JavaScriptCore/SourceOrigin.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
#include <mimalloc.h>
|
||||
|
||||
namespace Bake {
|
||||
|
||||
class DevServerSourceProvider;
|
||||
|
||||
class SourceMapJSONString {
|
||||
public:
|
||||
SourceMapJSONString(const char* ptr, size_t length)
|
||||
: m_ptr(ptr)
|
||||
, m_length(length)
|
||||
{
|
||||
}
|
||||
|
||||
~SourceMapJSONString()
|
||||
{
|
||||
if (m_ptr) {
|
||||
mi_free(const_cast<char*>(m_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
// Delete copy constructor and assignment operator to prevent double free
|
||||
SourceMapJSONString(const SourceMapJSONString&) = delete;
|
||||
SourceMapJSONString& operator=(const SourceMapJSONString&) = delete;
|
||||
|
||||
// Move constructor and assignment
|
||||
SourceMapJSONString(SourceMapJSONString&& other) noexcept
|
||||
: m_ptr(other.m_ptr)
|
||||
, m_length(other.m_length)
|
||||
{
|
||||
other.m_ptr = nullptr;
|
||||
other.m_length = 0;
|
||||
}
|
||||
|
||||
SourceMapJSONString& operator=(SourceMapJSONString&& other) noexcept
|
||||
{
|
||||
if (this != &other) {
|
||||
if (m_ptr) {
|
||||
mi_free(const_cast<char*>(m_ptr));
|
||||
}
|
||||
m_ptr = other.m_ptr;
|
||||
m_length = other.m_length;
|
||||
other.m_ptr = nullptr;
|
||||
other.m_length = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
const char* ptr() const { return m_ptr; }
|
||||
size_t length() const { return m_length; }
|
||||
|
||||
private:
|
||||
const char* m_ptr;
|
||||
size_t m_length;
|
||||
};
|
||||
|
||||
// Struct to return source map data to Zig
|
||||
struct SourceMapData {
|
||||
const char* ptr;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
// Function to be implemented in Zig to register the source provider
|
||||
extern "C" void Bun__addDevServerSourceProvider(void* bun_vm, DevServerSourceProvider* opaque_source_provider, BunString* specifier);
|
||||
|
||||
class DevServerSourceProvider final : public JSC::StringSourceProvider {
|
||||
public:
|
||||
static Ref<DevServerSourceProvider> create(
|
||||
JSC::JSGlobalObject* globalObject,
|
||||
const String& source,
|
||||
const char* sourceMapJSONPtr,
|
||||
size_t sourceMapJSONLength,
|
||||
const JSC::SourceOrigin& sourceOrigin,
|
||||
String&& sourceURL,
|
||||
const TextPosition& startPosition,
|
||||
JSC::SourceProviderSourceType sourceType)
|
||||
{
|
||||
auto provider = adoptRef(*new DevServerSourceProvider(source, sourceMapJSONPtr, sourceMapJSONLength, sourceOrigin, WTFMove(sourceURL), startPosition, sourceType));
|
||||
auto* zigGlobalObject = jsCast<::Zig::GlobalObject*>(globalObject);
|
||||
auto specifier = Bun::toString(provider->sourceURL());
|
||||
Bun__addDevServerSourceProvider(zigGlobalObject->bunVM(), provider.ptr(), &specifier);
|
||||
return provider;
|
||||
}
|
||||
|
||||
SourceMapData sourceMapJSON() const
|
||||
{
|
||||
return SourceMapData { m_sourceMapJSON.ptr(), m_sourceMapJSON.length() };
|
||||
}
|
||||
|
||||
private:
|
||||
DevServerSourceProvider(
|
||||
const String& source,
|
||||
const char* sourceMapJSONPtr,
|
||||
size_t sourceMapJSONLength,
|
||||
const JSC::SourceOrigin& sourceOrigin,
|
||||
String&& sourceURL,
|
||||
const TextPosition& startPosition,
|
||||
JSC::SourceProviderSourceType sourceType)
|
||||
: StringSourceProvider(
|
||||
source,
|
||||
sourceOrigin,
|
||||
JSC::SourceTaintedOrigin::Untainted,
|
||||
WTFMove(sourceURL),
|
||||
startPosition,
|
||||
sourceType)
|
||||
, m_sourceMapJSON(sourceMapJSONPtr, sourceMapJSONLength)
|
||||
{
|
||||
}
|
||||
|
||||
SourceMapJSONString m_sourceMapJSON;
|
||||
};
|
||||
|
||||
} // namespace Bake
|
||||
@@ -3,8 +3,6 @@ import { renderToHtml, renderToStaticHtml } from "bun-framework-react/ssr.tsx" w
|
||||
import { serverManifest } from "bun:bake/server";
|
||||
import { PassThrough } from "node:stream";
|
||||
import { renderToPipeableStream } from "react-server-dom-bun/server.node.unbundled.js";
|
||||
import type { AsyncLocalStorage } from "node:async_hooks";
|
||||
import type { RequestContext } from "../hmr-runtime-server";
|
||||
|
||||
function assertReactComponent(Component: any) {
|
||||
if (typeof Component !== "function") {
|
||||
@@ -14,8 +12,8 @@ function assertReactComponent(Component: any) {
|
||||
}
|
||||
|
||||
// This function converts the route information into a React component tree.
|
||||
function getPage(meta: Bake.RouteMetadata & { request?: Request }, styles: readonly string[]) {
|
||||
let route = component(meta.pageModule, meta.params, meta.request);
|
||||
function getPage(meta: Bake.RouteMetadata, styles: readonly string[]) {
|
||||
let route = component(meta.pageModule, meta.params);
|
||||
for (const layout of meta.layouts) {
|
||||
const Layout = layout.default;
|
||||
if (import.meta.env.DEV) assertReactComponent(Layout);
|
||||
@@ -37,7 +35,7 @@ function getPage(meta: Bake.RouteMetadata & { request?: Request }, styles: reado
|
||||
);
|
||||
}
|
||||
|
||||
function component(mod: any, params: Record<string, string> | null, request?: Request) {
|
||||
function component(mod: any, params: Record<string, string> | null) {
|
||||
const Page = mod.default;
|
||||
let props = {};
|
||||
if (import.meta.env.DEV) assertReactComponent(Page);
|
||||
@@ -51,21 +49,12 @@ function component(mod: any, params: Record<string, string> | null, request?: Re
|
||||
props = method();
|
||||
}
|
||||
|
||||
// Pass request prop if mode is 'ssr'
|
||||
if (mod.mode === "ssr" && request) {
|
||||
props.request = request;
|
||||
}
|
||||
|
||||
return <Page params={params} {...props} />;
|
||||
}
|
||||
|
||||
// `server.tsx` exports a function to be used for handling user routes. It takes
|
||||
// in the Request object, the route's module, extra route metadata, and the AsyncLocalStorage instance.
|
||||
export async function render(
|
||||
request: Request,
|
||||
meta: Bake.RouteMetadata,
|
||||
als?: AsyncLocalStorage<RequestContext>,
|
||||
): Promise<Response> {
|
||||
// in the Request object, the route's module, and extra route metadata.
|
||||
export async function render(request: Request, meta: Bake.RouteMetadata): Promise<Response> {
|
||||
// The framework generally has two rendering modes.
|
||||
// - Standard browser navigation
|
||||
// - Client-side navigation
|
||||
@@ -75,9 +64,6 @@ export async function render(
|
||||
// This is signaled by `client.tsx` via the `Accept` header.
|
||||
const skipSSR = request.headers.get("Accept")?.includes("text/x-component");
|
||||
|
||||
// Check if the page module has a streaming export, default to false
|
||||
const streaming = meta.pageModule.streaming ?? false;
|
||||
|
||||
// Do not render <link> tags if the request is skipping SSR.
|
||||
const page = getPage(meta, skipSSR ? [] : meta.styles);
|
||||
|
||||
@@ -97,119 +83,34 @@ export async function render(
|
||||
|
||||
// This renders Server Components to a ReadableStream "RSC Payload"
|
||||
let pipe;
|
||||
const signal: MiniAbortSignal = { aborted: undefined, abort: null! };
|
||||
const signal: MiniAbortSignal = { aborted: false, abort: null! };
|
||||
({ pipe, abort: signal.abort } = renderToPipeableStream(page, serverManifest, {
|
||||
onError: err => {
|
||||
// console.error("onError renderToPipeableStream", !!signal.aborted);
|
||||
if (signal.aborted) return;
|
||||
|
||||
// Mark as aborted and call the abort function
|
||||
signal.aborted = err;
|
||||
// @ts-expect-error
|
||||
signal.abort(err);
|
||||
rscPayload.destroy(err);
|
||||
console.error(err);
|
||||
},
|
||||
filterStackFrame: () => false,
|
||||
}));
|
||||
pipe(rscPayload);
|
||||
|
||||
rscPayload.on("error", err => {
|
||||
if (signal.aborted) return;
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
if (skipSSR) {
|
||||
const responseOptions = als?.getStore()?.responseOptions || {};
|
||||
return new Response(rscPayload as any, {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/x-component" },
|
||||
...responseOptions,
|
||||
});
|
||||
}
|
||||
|
||||
// The RSC payload is rendered into HTML
|
||||
if (streaming) {
|
||||
const responseOptions = als?.getStore()?.responseOptions || {};
|
||||
if (als) {
|
||||
const state = als.getStore();
|
||||
if (state) state.streamingStarted = true;
|
||||
}
|
||||
// Stream the response as before
|
||||
return new Response(renderToHtml(rscPayload, meta.modules, signal), {
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf8",
|
||||
},
|
||||
...responseOptions,
|
||||
});
|
||||
} else {
|
||||
// FIXME: this is bad and could be done way better
|
||||
// FIXME: why are we even doing stream stuff is `streaming=false`, is there a way to do RSC without stream
|
||||
|
||||
// Set up the render abort handler for non-streaming mode
|
||||
if (als) {
|
||||
const store = als.getStore();
|
||||
if (store) {
|
||||
store.renderAbort = (path: string, params: Record<string, any> | null) => {
|
||||
// Create the abort error
|
||||
const abortError = new (globalThis as any).RenderAbortError(path, params);
|
||||
// Abort the current render
|
||||
signal.aborted = abortError;
|
||||
signal.abort(abortError);
|
||||
rscPayload.destroy(abortError);
|
||||
throw abortError;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer the entire response and return it all at once
|
||||
const htmlStream = renderToHtml(rscPayload, meta.modules, signal);
|
||||
const chunks: Uint8Array[] = [];
|
||||
const reader = htmlStream.getReader();
|
||||
|
||||
try {
|
||||
let keepGoing = true;
|
||||
do {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
// Check if the render was aborted with an error
|
||||
if (signal.aborted) {
|
||||
// If it's a RenderAbortError, re-throw it to be handled upstream
|
||||
if (signal.aborted instanceof (globalThis as any).RenderAbortError) {
|
||||
throw signal.aborted;
|
||||
}
|
||||
// For some reason in react-server-dom the `stream.on("error")`
|
||||
// handler creates a new Error???
|
||||
if (signal.aborted.message !== "Connection closed.") {
|
||||
// For other errors, we can handle them here or re-throw
|
||||
throw signal.aborted;
|
||||
}
|
||||
}
|
||||
|
||||
keepGoing = !done;
|
||||
if (!done) {
|
||||
chunks.push(value);
|
||||
}
|
||||
} while (keepGoing);
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
|
||||
// Combine all chunks into a single response
|
||||
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
const opts = als?.getStore()?.responseOptions ?? { headers: {} };
|
||||
const { headers, ...response_options } = opts;
|
||||
|
||||
return new Response(result, {
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf8",
|
||||
"Set-Cookie": request.cookies.toSetCookieHeaders(),
|
||||
...headers,
|
||||
},
|
||||
...response_options,
|
||||
});
|
||||
}
|
||||
return new Response(await renderToHtml(rscPayload, meta.modules, signal), {
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf8",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// When a production build is performed, pre-rendering is invoked here. If this
|
||||
@@ -227,13 +128,7 @@ export async function prerender(meta: Bake.RouteMetadata) {
|
||||
let rscChunks: Array<BlobPart> = [int.buffer as ArrayBuffer, meta.styles.join("\n")];
|
||||
rscPayload.on("data", chunk => rscChunks.push(chunk));
|
||||
|
||||
let html;
|
||||
try {
|
||||
html = await renderToStaticHtml(rscPayload, meta.modules);
|
||||
} catch (err) {
|
||||
//console.error("ah fuck");
|
||||
return undefined;
|
||||
}
|
||||
const html = await renderToStaticHtml(rscPayload, meta.modules);
|
||||
const rsc = new Blob(rscChunks, { type: "text/x-component" });
|
||||
|
||||
return {
|
||||
@@ -289,7 +184,7 @@ export const contentTypeToStaticFile = {
|
||||
|
||||
/** Instead of using AbortController, this is used */
|
||||
export interface MiniAbortSignal {
|
||||
aborted: Error | undefined;
|
||||
aborted: boolean;
|
||||
/** Caller must set `aborted` to true before calling. */
|
||||
abort: () => void;
|
||||
}
|
||||
|
||||
@@ -56,12 +56,6 @@ export function renderToHtml(
|
||||
// with `use`, and then returning the parsed React component for the UI.
|
||||
const Root: any = () => React.use(promise);
|
||||
|
||||
// If the signal is already aborted, we should not proceed
|
||||
if (signal.aborted) {
|
||||
controller.close(signal.aborted);
|
||||
return Promise.reject(signal.aborted);
|
||||
}
|
||||
|
||||
// `renderToPipeableStream` is what actually generates HTML.
|
||||
// Here is where React is told what script tags to inject.
|
||||
let pipe: (stream: any) => void;
|
||||
@@ -69,13 +63,7 @@ export function renderToHtml(
|
||||
bootstrapModules,
|
||||
onError(error) {
|
||||
if (!signal.aborted) {
|
||||
// Abort the rendering and close the stream
|
||||
signal.aborted = error;
|
||||
abort();
|
||||
if (signal.abort) signal.abort();
|
||||
if (stream) {
|
||||
stream.controller.close();
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -86,12 +74,10 @@ export function renderToHtml(
|
||||
// Promise resolved after all data is combined.
|
||||
return stream.finished;
|
||||
},
|
||||
cancel(err) {
|
||||
if (!signal.aborted) {
|
||||
signal.aborted = err;
|
||||
signal.abort(err);
|
||||
}
|
||||
abort?.(err);
|
||||
cancel() {
|
||||
signal.aborted = true;
|
||||
signal.abort();
|
||||
abort?.();
|
||||
},
|
||||
} as Bun.DirectUnderlyingSource as any);
|
||||
}
|
||||
@@ -147,28 +133,19 @@ class RscInjectionStream extends EventEmitter {
|
||||
/** Resolved when all data is written */
|
||||
finished: Promise<void>;
|
||||
finalize: () => void;
|
||||
reject: (err: any) => void;
|
||||
|
||||
constructor(rscPayload: Readable, controller: ReadableStreamDirectController) {
|
||||
super();
|
||||
this.controller = controller;
|
||||
|
||||
const { resolve, promise, reject } = Promise.withResolvers<void>();
|
||||
const { resolve, promise } = Promise.withResolvers<void>();
|
||||
this.finished = promise;
|
||||
this.finalize = resolve;
|
||||
this.reject = reject;
|
||||
|
||||
rscPayload.on("data", this.writeRscData.bind(this));
|
||||
rscPayload.on("end", () => {
|
||||
this.rscHasEnded = true;
|
||||
});
|
||||
rscPayload.on("error", err => {
|
||||
this.rscHasEnded = true;
|
||||
// Close the controller
|
||||
controller.close();
|
||||
// Reject the promise instead of resolving it
|
||||
this.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
write(data: Uint8Array) {
|
||||
@@ -307,8 +284,7 @@ class StaticRscInjectionStream extends EventEmitter {
|
||||
}
|
||||
|
||||
destroy(error) {
|
||||
// We don't need to console.error here as react does it itself
|
||||
// console.error(error);
|
||||
console.error(error);
|
||||
this.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +115,6 @@ export class HMRModule {
|
||||
: null;
|
||||
}
|
||||
|
||||
// Module Ids are pre-resolved by the bundler
|
||||
requireResolve(id: Id): Id {
|
||||
return id;
|
||||
}
|
||||
|
||||
require(id: Id) {
|
||||
try {
|
||||
const mod = loadModuleSync(id, true, this);
|
||||
|
||||
@@ -3,48 +3,11 @@
|
||||
import type { Bake } from "bun";
|
||||
import "./debug";
|
||||
import { loadExports, replaceModules, serverManifest, ssrManifest } from "./hmr-module";
|
||||
// import { AsyncLocalStorage } from "node:async_hooks";
|
||||
const { AsyncLocalStorage } = require("node:async_hooks");
|
||||
|
||||
if (typeof IS_BUN_DEVELOPMENT !== "boolean") {
|
||||
throw new Error("DCE is configured incorrectly");
|
||||
}
|
||||
|
||||
export type RequestContext = {
|
||||
responseOptions: ResponseInit;
|
||||
streamingStarted?: boolean;
|
||||
renderAbort?: (path: string, params: Record<string, any> | null) => never;
|
||||
};
|
||||
|
||||
// Create the AsyncLocalStorage instance for propagating response options
|
||||
const responseOptionsALS = new AsyncLocalStorage();
|
||||
|
||||
/// Created when the user does `return Response.render(...)`
|
||||
class RenderAbortError extends Error {
|
||||
constructor(
|
||||
public path: string,
|
||||
public params: Record<string, any> | null,
|
||||
public response: Response,
|
||||
) {
|
||||
super("Response.render() called");
|
||||
this.name = "RenderAbortError";
|
||||
}
|
||||
}
|
||||
|
||||
/// Created when the user does `return Response.redirect(...)`
|
||||
class RedirectAbortError extends Error {
|
||||
constructor(
|
||||
public response: Response,
|
||||
) {
|
||||
super("Response.redirect() called");
|
||||
this.name = "RedirectAbortError";
|
||||
}
|
||||
}
|
||||
|
||||
// Make RenderAbortError and RedirectAbortError globally available for other modules
|
||||
(globalThis as any).RenderAbortError = RenderAbortError;
|
||||
(globalThis as any).RedirectAbortError = RedirectAbortError;
|
||||
|
||||
interface Exports {
|
||||
handleRequest: (
|
||||
req: Request,
|
||||
@@ -53,8 +16,6 @@ interface Exports {
|
||||
clientEntryUrl: string,
|
||||
styles: string[],
|
||||
params: Record<string, string> | null,
|
||||
setAsyncLocalStorage: Function,
|
||||
getAsyncLocalStorage: Function,
|
||||
) => any;
|
||||
registerUpdate: (
|
||||
modules: any,
|
||||
@@ -65,20 +26,7 @@ interface Exports {
|
||||
|
||||
declare let server_exports: Exports;
|
||||
server_exports = {
|
||||
async handleRequest(
|
||||
req,
|
||||
routerTypeMain,
|
||||
routeModules,
|
||||
clientEntryUrl,
|
||||
styles,
|
||||
params,
|
||||
setAsyncLocalStorage,
|
||||
getAsyncLocalStorage,
|
||||
) {
|
||||
// FIXME: We should only have to do this once
|
||||
// Set the AsyncLocalStorage instance in the VM
|
||||
setAsyncLocalStorage(responseOptionsALS);
|
||||
|
||||
async handleRequest(req, routerTypeMain, routeModules, clientEntryUrl, styles, params) {
|
||||
if (IS_BUN_DEVELOPMENT && process.env.BUN_DEBUG_BAKE_JS) {
|
||||
console.log("handleRequest", {
|
||||
routeModules,
|
||||
@@ -100,67 +48,20 @@ server_exports = {
|
||||
}
|
||||
|
||||
const [pageModule, ...layouts] = await Promise.all(routeModules.map(loadExports));
|
||||
const response = await serverRenderer(req, {
|
||||
styles: styles,
|
||||
modules: [clientEntryUrl],
|
||||
layouts,
|
||||
pageModule,
|
||||
modulepreload: [],
|
||||
params,
|
||||
});
|
||||
|
||||
// Add cookies to request when mode is 'ssr'
|
||||
let requestWithCookies = req;
|
||||
if (pageModule.mode === "ssr") {
|
||||
requestWithCookies.cookies = req.cookies || new Bun.CookieMap(req.headers.get("Cookie") || "");
|
||||
if (!(response instanceof Response)) {
|
||||
throw new Error(`Server-side request handler was expected to return a Response object.`);
|
||||
}
|
||||
|
||||
let storeValue: RequestContext = {
|
||||
responseOptions: {},
|
||||
};
|
||||
|
||||
try {
|
||||
// Run the renderer inside the AsyncLocalStorage context
|
||||
// This allows Response constructors to access the stored options
|
||||
const response = await responseOptionsALS.run(storeValue, async () => {
|
||||
return await serverRenderer(
|
||||
requestWithCookies,
|
||||
{
|
||||
styles: styles,
|
||||
modules: [clientEntryUrl],
|
||||
layouts,
|
||||
pageModule,
|
||||
modulepreload: [],
|
||||
params,
|
||||
// Pass request in metadata when mode is 'ssr'
|
||||
request: pageModule.mode === "ssr" ? requestWithCookies : undefined,
|
||||
},
|
||||
responseOptionsALS,
|
||||
);
|
||||
});
|
||||
|
||||
if (!(response instanceof Response)) {
|
||||
throw new Error(`Server-side request handler was expected to return a Response object.`);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Handle Response.render() aborts
|
||||
if (error instanceof RenderAbortError) {
|
||||
// TODO: Implement route resolution to get the new route modules
|
||||
// For now, we'll need to get this information from the native side
|
||||
// The native code will need to resolve the route and call handleRequest again
|
||||
|
||||
// Store the render error info so native code can access it
|
||||
(globalThis as any).__lastRenderAbort = {
|
||||
path: error.path,
|
||||
params: error.params,
|
||||
};
|
||||
|
||||
// return it so the Zig code can handle it and re-render new route
|
||||
return error.response;
|
||||
}
|
||||
|
||||
// Handle Response.redirect() aborts
|
||||
if (error instanceof RedirectAbortError) {
|
||||
// Return the redirect response directly
|
||||
return error.response;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
return response;
|
||||
},
|
||||
async registerUpdate(modules, componentManifestAdd, componentManifestDelete) {
|
||||
replaceModules(modules);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Implements building a Bake application to production
|
||||
const log = bun.Output.scoped(.production, .visible);
|
||||
const log = bun.Output.scoped(.production, false);
|
||||
|
||||
pub fn buildCommand(ctx: bun.cli.Command.Context) !void {
|
||||
bun.bake.printWarning();
|
||||
@@ -26,7 +26,7 @@ pub fn buildCommand(ctx: bun.cli.Command.Context) !void {
|
||||
bun.ast.Expr.Data.Store.create();
|
||||
bun.ast.Stmt.Data.Store.create();
|
||||
|
||||
var arena = bun.MimallocArena.init();
|
||||
var arena = try bun.MimallocArena.init();
|
||||
defer arena.deinit();
|
||||
|
||||
const vm = try VirtualMachine.initBake(.{
|
||||
@@ -184,33 +184,11 @@ pub fn buildWithVm(ctx: bun.cli.Command.Context, cwd: []const u8, vm: *VirtualMa
|
||||
const default = BakeGetDefaultExportFromModule(vm.global, config_entry_point_string.toJS(vm.global));
|
||||
|
||||
if (!default.isObject()) {
|
||||
return global.throwInvalidArguments(
|
||||
\\Your config file's default export must be an object.
|
||||
\\
|
||||
\\Example:
|
||||
\\ export default {
|
||||
\\ app: {
|
||||
\\ framework: "react",
|
||||
\\ }
|
||||
\\ }
|
||||
\\
|
||||
\\Learn more at https://bun.com/docs/ssg
|
||||
, .{});
|
||||
Output.panic("TODO: print this error better, default export is not an object", .{});
|
||||
}
|
||||
|
||||
const app = try default.get(vm.global, "app") orelse {
|
||||
return global.throwInvalidArguments(
|
||||
\\Your config file's default export must contain an "app" property.
|
||||
\\
|
||||
\\Example:
|
||||
\\ export default {
|
||||
\\ app: {
|
||||
\\ framework: "react",
|
||||
\\ }
|
||||
\\ }
|
||||
\\
|
||||
\\Learn more at https://bun.com/docs/ssg
|
||||
, .{});
|
||||
Output.panic("TODO: print this error better, default export needs an 'app' object", .{});
|
||||
};
|
||||
|
||||
break :config try bake.UserOptions.fromJS(app, vm.global);
|
||||
@@ -431,7 +409,7 @@ pub fn buildWithVm(ctx: bun.cli.Command.Context, cwd: []const u8, vm: *VirtualMa
|
||||
},
|
||||
.asset => {},
|
||||
.bytecode => {},
|
||||
.sourcemap => {},
|
||||
.sourcemap => @panic("TODO: register source map"),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pub const Run = struct {
|
||||
|
||||
js_ast.Expr.Data.Store.create();
|
||||
js_ast.Stmt.Data.Store.create();
|
||||
const arena = Arena.init();
|
||||
const arena = try Arena.init();
|
||||
|
||||
if (!ctx.debug.loaded_bunfig) {
|
||||
try bun.cli.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", ctx, .RunCommand);
|
||||
@@ -31,7 +31,7 @@ pub const Run = struct {
|
||||
|
||||
run = .{
|
||||
.vm = try VirtualMachine.initWithModuleGraph(.{
|
||||
.allocator = arena.allocator(),
|
||||
.allocator = bun.default_allocator,
|
||||
.log = ctx.log,
|
||||
.args = ctx.args,
|
||||
.graph = graph_ptr,
|
||||
@@ -48,7 +48,7 @@ pub const Run = struct {
|
||||
vm.preload = ctx.preloads;
|
||||
vm.argv = ctx.passthrough;
|
||||
vm.arena = &run.arena;
|
||||
vm.allocator = arena.allocator();
|
||||
vm.allocator = bun.default_allocator;
|
||||
|
||||
b.options.install = ctx.install;
|
||||
b.resolver.opts.install = ctx.install;
|
||||
@@ -160,12 +160,12 @@ pub const Run = struct {
|
||||
|
||||
js_ast.Expr.Data.Store.create();
|
||||
js_ast.Stmt.Data.Store.create();
|
||||
const arena = Arena.init();
|
||||
const arena = try Arena.init();
|
||||
|
||||
run = .{
|
||||
.vm = try VirtualMachine.init(
|
||||
.{
|
||||
.allocator = arena.allocator(),
|
||||
.allocator = bun.default_allocator,
|
||||
.log = ctx.log,
|
||||
.args = ctx.args,
|
||||
.store_fd = ctx.debug.hot_reload != .none,
|
||||
@@ -187,7 +187,7 @@ pub const Run = struct {
|
||||
vm.preload = ctx.preloads;
|
||||
vm.argv = ctx.passthrough;
|
||||
vm.arena = &run.arena;
|
||||
vm.allocator = arena.allocator();
|
||||
vm.allocator = bun.default_allocator;
|
||||
|
||||
if (ctx.runtime_options.eval.script.len > 0) {
|
||||
const script_source = try bun.default_allocator.create(logger.Source);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user