mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
merge upstream 0.28.2+ (34 commits)
Disjoint lineage: our fork point has the same tree as upstream 6ff1d8b0d but a
different SHA, so a plain merge would diff against the root. Merged with
upstream as a second parent and the fork-point..upstream/main patch applied, so
the tree is exactly upstream/main plus our two files.
Includes the symlink 403 for keyfile/certfile and the input-file overwrite fix.
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+18
-114
@@ -8,7 +8,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
esbuild-platforms:
|
||||
@@ -18,20 +18,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # actions/checkout@v3
|
||||
|
||||
- name: Read go.version
|
||||
run: |
|
||||
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
id: go
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
- name: Set up Node 18
|
||||
uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
@@ -46,20 +46,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # actions/checkout@v3
|
||||
|
||||
- name: Read go.version
|
||||
run: |
|
||||
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
id: go
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
- name: Set up Node 16
|
||||
uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # actions/checkout@v3
|
||||
|
||||
- name: Read go.version (non-Windows)
|
||||
if: matrix.os != 'windows-latest'
|
||||
@@ -105,35 +105,24 @@ jobs:
|
||||
echo "GO_VERSION=$(cat go.version)" >> $Env:GITHUB_ENV
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
id: go
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
- name: Set up Node 18
|
||||
uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Setup Deno 1.40.0
|
||||
uses: denoland/setup-deno@main
|
||||
with:
|
||||
deno-version: v1.40.0
|
||||
|
||||
- name: go test
|
||||
run: go test -race ./internal/...
|
||||
env:
|
||||
WT_SESSION: 1 # Ensure that FormatMessages output on Windows matches macOS
|
||||
run: go test -race ./cmd/... ./internal/... ./pkg/...
|
||||
|
||||
- name: go vet
|
||||
run: go vet ./cmd/... ./internal/... ./pkg/...
|
||||
|
||||
- name: Deno Tests (non-Windows)
|
||||
if: matrix.os != 'windows-latest'
|
||||
run: make test-deno
|
||||
|
||||
- name: Deno Tests (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: make test-deno-windows
|
||||
|
||||
- name: Test for path/filepath
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: make no-filepath
|
||||
@@ -230,10 +219,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # actions/checkout@v3
|
||||
|
||||
- name: Set up Go 1.13 (the minimum required Go version for esbuild)
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.13
|
||||
id: go
|
||||
@@ -243,88 +232,3 @@ jobs:
|
||||
|
||||
- name: go test
|
||||
run: go test ./internal/...
|
||||
|
||||
esbuild-old-deno-version:
|
||||
name: esbuild CI (old Deno version)
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# Note: I'm excluding "macos-latest" here because GitHub recently
|
||||
# changed their macOS CI VMs from x86_64 to aarch64 (i.e. from Intel
|
||||
# to ARM) and it looks like old Deno versions have WASM bugs on ARM.
|
||||
# Specifically, this test now crashes like this when run on macOS:
|
||||
#
|
||||
# #
|
||||
# # Fatal error in , line 0
|
||||
# # Check failed: RwxMemoryWriteScope::IsAllowed().
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #FailureMessage Object: 0x16f282368
|
||||
# ==== C stack trace ===============================
|
||||
#
|
||||
# 0 deno 0x0000000101bb8e78 v8::base::debug::StackTrace::StackTrace() + 24
|
||||
# 1 deno 0x0000000101bbda84 v8::platform::(anonymous namespace)::PrintStackTrace() + 24
|
||||
# 2 deno 0x0000000101bb6230 V8_Fatal(char const*, ...) + 268
|
||||
# 3 deno 0x000000010227e468 v8::internal::wasm::WasmCodeManager::MemoryProtectionKeysEnabled() const + 0
|
||||
# 4 deno 0x0000000102299994 v8::internal::wasm::WasmEngine::InitializeOncePerProcess() + 44
|
||||
# 5 deno 0x0000000101e78fd0 v8::internal::V8::Initialize() + 1576
|
||||
# 6 deno 0x0000000101c3b7d8 v8::V8::Initialize(int) + 32
|
||||
# 7 deno 0x00000001011833dc _ZN3std4sync4once4Once9call_once28_$u7b$$u7b$closure$u7d$$u7d$17h2bbe74d315ab3e84E + 488
|
||||
# 8 deno 0x00000001017f8854 std::sync::once::Once::call_inner::h70fbdd48fe002a01 + 724
|
||||
# 9 deno 0x000000010115ca80 deno_core::runtime::JsRuntime::new::h9c5f1a9c910f1eed + 192
|
||||
# 10 deno 0x00000001014d3b50 deno_runtime::worker::MainWorker::bootstrap_from_options::h91a0eaac48dfc18e + 4260
|
||||
# 11 deno 0x0000000100ee692c deno::create_main_worker::h0d1622755821ae7f + 1608
|
||||
# 12 deno 0x0000000100f6c688 _ZN97_$LT$core..future..from_generator..GenFuture$LT$T$GT$$u20$as$u20$core..future..future..Future$GT$4poll17h87ddfac9566887c8E + 492
|
||||
# 13 deno 0x0000000100f6ba18 tokio::runtime::task::raw::poll::h7d51f1a7d5a61c15 + 1396
|
||||
# 14 deno 0x0000000101917b98 std::sys_common::backtrace::__rust_begin_short_backtrace::hd384935dcffe6f2d + 332
|
||||
# 15 deno 0x0000000101917954 _ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17he2755732d5d29cf0E + 124
|
||||
# 16 deno 0x0000000101829684 std::sys::unix::thread::Thread::new::thread_start::h432bc30153e41f60 + 48
|
||||
# 17 libsystem_pthread.dylib 0x000000018a436f94 _pthread_start + 136
|
||||
# 18 libsystem_pthread.dylib 0x000000018a431d34 thread_start + 8
|
||||
#
|
||||
# Hopefully running this old Deno version on Linux is sufficiently
|
||||
# close to running it on macOS. For reference, I believe this is the
|
||||
# change that GitHub made which broke this test:
|
||||
# https://github.blog/changelog/2023-10-02-github-actions-apple-silicon-m1-macos-runners-are-now-available-in-public-beta/
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Read go.version (non-Windows)
|
||||
if: matrix.os != 'windows-latest'
|
||||
run: |
|
||||
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
|
||||
|
||||
- name: Read go.version (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
echo "GO_VERSION=$(cat go.version)" >> $Env:GITHUB_ENV
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
id: go
|
||||
|
||||
# Make sure esbuild works with old versions of Deno. Note: It's important
|
||||
# to test a version before 1.31.0, which introduced the "Deno.Command" API.
|
||||
- name: Setup Deno 1.24.0
|
||||
uses: denoland/setup-deno@main
|
||||
with:
|
||||
deno-version: v1.24.0
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Deno Tests (non-Windows)
|
||||
if: matrix.os != 'windows-latest'
|
||||
run: make test-deno
|
||||
|
||||
- name: Deno Tests (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: make test-deno-windows
|
||||
|
||||
@@ -6,27 +6,20 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
- name: Set up Node 18
|
||||
uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
# The version of Deno is pinned because version 1.25.1 was causing test
|
||||
# flakes due to random segfaults.
|
||||
- name: Setup Deno 1.24.0
|
||||
uses: denoland/setup-deno@main
|
||||
with:
|
||||
deno-version: v1.24.0
|
||||
|
||||
- name: Test npm
|
||||
run: |
|
||||
npm i -g npm@next-7
|
||||
@@ -46,7 +39,3 @@ jobs:
|
||||
run: |
|
||||
npm i -g yarn@latest
|
||||
time make test-e2e-yarn-berry
|
||||
|
||||
- name: Test deno
|
||||
run: |
|
||||
time make test-e2e-deno
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # actions/checkout@v3
|
||||
|
||||
- name: Read version info
|
||||
run: |
|
||||
@@ -42,12 +42,12 @@ jobs:
|
||||
test -n "$CHANGELOG"
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
- name: Set up Node 24
|
||||
uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # actions/setup-node@v3
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
test -z "$(git status --porcelain)"
|
||||
|
||||
# Trusted publishing requires this specific version of npm
|
||||
- name: Install npm
|
||||
- name: Install npm 11.5.1
|
||||
run: |
|
||||
npm install -g [email protected]
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
# Only do this after publishing was successful
|
||||
- name: Create a GitHub Release
|
||||
uses: actions/create-release@v1
|
||||
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -6,21 +6,21 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # actions/checkout@v3
|
||||
|
||||
- name: Read go.version
|
||||
run: |
|
||||
echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
id: go
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
.vscode/
|
||||
/bench/
|
||||
/demo/
|
||||
/deno/
|
||||
/esbuild
|
||||
/github/
|
||||
/go/
|
||||
/lib/deno/lib.deno.d.ts
|
||||
/npm/@esbuild/android-arm/esbuild.wasm
|
||||
/npm/@esbuild/android-arm/wasm_exec_node.js
|
||||
/npm/@esbuild/android-arm/wasm_exec.js
|
||||
|
||||
+148
@@ -1,5 +1,153 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Allow `es2026` as a target in `tsconfig.json`
|
||||
|
||||
TypeScript is [adding `es2026`](https://github.com/microsoft/TypeScript/issues/63704) as a compilation target, so esbuild now supports this in the `target` field of `tsconfig.json` files, such as in the following configuration file:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2026"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As a reminder, the only thing that esbuild uses this field for is determining whether or not to use legacy TypeScript behavior for class fields. You can read more in [the documentation](https://esbuild.github.io/content-types/#tsconfig-json).
|
||||
|
||||
* Stop publishing to https://deno.land/x/esbuild
|
||||
|
||||
Deno has made the https://deno.land/x package registry read-only, so new versions of esbuild can no longer be published to https://deno.land/x/esbuild. The last published version was [v0.28.1](https://deno.land/x/[email protected]). I have removed the parts of esbuild that publish a Deno-specific package. Instead you can install `npm:esbuild` to use esbuild in Deno (this wasn't the case when esbuild's Deno-specific package was first published).
|
||||
|
||||
* Emit an error when code splitting chunks would be merged ([#4411](https://github.com/evanw/esbuild/issues/4411))
|
||||
|
||||
It's possible to configure esbuild such that separate output files end up with the same output path. For example, you could name all code splitting chunks `chunk` via the `chunkNames` setting, which might generate multiple output files with the same path `chunk.js`. This does not happen by default since by default the chunk names include a hash to make sure they're unique (e.g. named something like `chunk-GX7G2SBE.js` instead).
|
||||
|
||||
Previously esbuild allowed output files to be merged if both the file path and content were the same. This behavior was intended for assets (e.g. images) but is not appropriate for code, as code modules may still have their own internal state that needs to stay separate. This configuration is no longer allowed starting with this release. Doing this is now a build error. If your code structure generates conflicting chunk names, then you should make sure the chunk names include a placeholder for the hash.
|
||||
|
||||
## 0.28.2
|
||||
|
||||
* Fix tree shaking bug due to TypeScript import alias ([#4507](https://github.com/evanw/esbuild/issues/4507))
|
||||
|
||||
This release fixes a bug that could cause esbuild to incorrectly tree-shake imports that are used in a TypeScript type alias under certain circumstances. Affected code uses a TypeScript-specific `import` assignment and looks something like this:
|
||||
|
||||
```ts
|
||||
import Base from './dep.js';
|
||||
import Alias = Base.SomeType;
|
||||
```
|
||||
|
||||
* Fix CSS minification bug involving `&` ([#4497](https://github.com/evanw/esbuild/issues/4497))
|
||||
|
||||
This release fixes a bug where esbuild's CSS minifier incorrectly removed a `&` when it was unsafe to do so. Here is an example:
|
||||
|
||||
```css
|
||||
/* Original code */
|
||||
.a .b {
|
||||
& .b:not(& .c) {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
/* Old output (with --minify) */
|
||||
.a .b{.b:not(& .c){color:red}}
|
||||
|
||||
/* New output (with --minify) */
|
||||
.a .b{& .b:not(& .c){color:red}}
|
||||
```
|
||||
|
||||
This should match `<span class="a"><span class="b"><span class="b">yes</span></span></span>` but not `<span class="a"><span class="b">no</span></span>`. The old output incorrectly matched both.
|
||||
|
||||
* Avoid overwriting input files without `--allow-overwrite` ([#4484](https://github.com/evanw/esbuild/issues/4484))
|
||||
|
||||
For example: `esbuild input.js --outfile=input.js` tells esbuild to overwrite `input.js` with the output of running esbuild on it. This was supposed to already be prevented by default, but it accidentally regressed in version 0.17.0 and apparently didn't have any test coverage. The error message was being printed but the input file was still being overwritten. Oops.
|
||||
|
||||
This release puts the original behavior back. With this release, esbuild should now actually avoid overwriting input files unless `--allow-overwrite` is explicitly present. This is done by not writing out any files when a build error is encountered.
|
||||
|
||||
* Fix incorrect code generated when using top-level await ([#4498](https://github.com/evanw/esbuild/issues/4498))
|
||||
|
||||
Previously esbuild could generate code containing a syntax error in complex scenarios involving top-level await used in a dependency cycle. The problem was a missing `async` on one or more module wrapper closures. With this release, esbuild now uses a fixed-point iteration algorithm to correctly annotate all dependencies in the cycle as needing an `async` module wrapper.
|
||||
|
||||
* Fix a minification bug with lowered logical assignment operators ([#4508](https://github.com/evanw/esbuild/issues/4508))
|
||||
|
||||
This release fixes a bug that could cause esbuild to generate incorrect code for logical assignment operators when lowering them to an older target environment. Specifically the lowering process requires duplicating the left-hand side, but esbuild incorrectly failed to count the duplicate as a new usage when the left-hand side is an identifier. That then caused the minifier to believe that the left-hand side was only used once and could attempt to incorrectly inline an initializer into the first usage. This bug has now been fixed:
|
||||
|
||||
```js
|
||||
// Original code
|
||||
function foo() {
|
||||
let x
|
||||
bar(x ||= {})
|
||||
}
|
||||
|
||||
// Old output (with --minify-syntax --target=es6)
|
||||
function foo() {
|
||||
bar(void 0 || (x = {}));
|
||||
}
|
||||
|
||||
// New output (with --minify-syntax --target=es6)
|
||||
function foo() {
|
||||
let x;
|
||||
bar(x || (x = {}));
|
||||
}
|
||||
```
|
||||
|
||||
* Fix a potential deadlock when the JavaScript API is used incorrectly ([#4503](https://github.com/evanw/esbuild/issues/4503), [#4506](https://github.com/evanw/esbuild/pull/4506))
|
||||
|
||||
The JavaScript API runs the native esbuild executable as a long-lived child process and communicates with it over stdin/stdout/stderr. Each API request is asynchronous and the executable stays open as long as it has work to do, which is as long as either stdin is still open (meaning there may be more API requests) or there are currently requests being processed.
|
||||
|
||||
Previously esbuild's tracking of outstanding API requests missed decrementing a reference count in an edge case where esbuild's JavaScript API was used incorrectly and the API request returned an error. This could in some cases cause esbuild's native executable to exit with an error message about a deadlock. This release fixes the reference counting bug.
|
||||
|
||||
This fix was submitted by [@ZuBB](https://github.com/ZuBB).
|
||||
|
||||
* Handle target collisions ([#4509](https://github.com/evanw/esbuild/issues/4509))
|
||||
|
||||
It's possible to specify the same target engine multiple times, such as with `--target=chrome1,chrome99`. This edge case wasn't anticipated and previously took the last version for the duplicated target engine instead of the minimum version (so `chrome99` in this case instead of `chrome1`). With this release, esbuild will now pick the minimum version between all duplicated target engines.
|
||||
|
||||
* Force `.mp3` files to use the `audio/mpeg` MIME type ([#4485](https://github.com/evanw/esbuild/issues/4485))
|
||||
|
||||
MIME type detection for esbuild's data URLs uses Go's built-in MIME type detection, which is based on the [MIME sniffing standard](https://mimesniff.spec.whatwg.org/). This works correctly for MP3 files that start with the byte sequence `ID3`, which is commonly the case. However, it's possible to construct valid MP3 files that do not start with `ID3`, and that perhaps Go's built-in MIME type detection doesn't implement the "Signature for MP3 without ID3" part of the algorithm. This results in some `.mp3` files incorrectly using the `application/octet-stream` MIME type instead of `audio/mpeg`. With this release, esbuild will now always use the `audio/mpeg` MIME type for files ending in `.mp3`.
|
||||
|
||||
* Add a new TypeScript syntax warning
|
||||
|
||||
TypeScript 7 turned some previously-valid TypeScript syntax into a syntax error because it was confusing. TypeScript 6 accepts `1 + 2 as number * 3` as valid syntax but confusingly converts it to `(1 + 2) * 3` instead of the more intuitive conversion to `1 + (2 * 3)`. This syntax is now an error in TypeScript 7+. With this release, esbuild will now warn about the use of this syntax:
|
||||
|
||||
```ts
|
||||
▲ [WARNING] Operator "*" should not directly follow a TypeScript type cast after the "+" operator [confusing-typescript-cast]
|
||||
|
||||
example.ts:1:28:
|
||||
1 │ console.log(1 + 2 as number * 3)
|
||||
╵ ^
|
||||
|
||||
This is a syntax error in newer versions of TypeScript because the type cast has unintuitive
|
||||
precedence in this case. Surround the inner expression in parentheses to silence this warning:
|
||||
|
||||
example.ts:1:12:
|
||||
1 │ console.log(1 + 2 as number * 3)
|
||||
│ ~~~~~~~~~~~~~~~
|
||||
╵ ( )
|
||||
```
|
||||
|
||||
See [microsoft/TypeScript#63527](https://github.com/microsoft/TypeScript/issues/63527) for more information.
|
||||
|
||||
* Add support for formatting errors for Visual Studio ([#4460](https://github.com/evanw/esbuild/issues/4460))
|
||||
|
||||
Visual Studio has a specific style that it expects log messages to be in for them to show up in the UI when esbuild is run as a custom build step. The current log style that esbuild uses doesn't conform to this specific style.
|
||||
|
||||
With this release, esbuild has a new log style for Visual Studio (and other tools in the MSBuild ecosystem) that can be enabled with `--log-style=visualstudio`. Here is an example log message in this style:
|
||||
|
||||
```
|
||||
$ esbuild example.ts --log-style=visualstudio
|
||||
/Users/evan/dev/esbuild/example.ts(1,29): warning ES0010: Operator "*" should not directly follow a TypeScript type cast after the "+" operator
|
||||
```
|
||||
|
||||
This log style is also available via the JS and Go APIs, and can now be used with the existing `formatMessages` API.
|
||||
|
||||
* Fix a bug with CSS gamut mapping ([#4488](https://github.com/evanw/esbuild/pull/4488))
|
||||
|
||||
Due to a typo, the fallback colors generated for CSS colors outside of the sRGB gamut weren't correct. This release fixes the generated colors to use the intended algorithm.
|
||||
|
||||
This fix was submitted by [@chatman-media](https://github.com/chatman-media).
|
||||
|
||||
## 0.28.1
|
||||
|
||||
* Disallow ``\`` in local development server HTTP requests ([GHSA-g7r4-m6w7-qqqr](https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr))
|
||||
|
||||
@@ -24,7 +24,7 @@ test-common: test-go vet-go no-filepath verify-source-map end-to-end-tests js-ap
|
||||
|
||||
# These tests are for release (the extra tests are not included in "test" because they are pretty slow)
|
||||
test-all:
|
||||
@$(MAKE) --no-print-directory -j6 test-common test-deno ts-type-tests test-wasm-node test-wasm-browser lib-typecheck test-yarnpnp
|
||||
@$(MAKE) --no-print-directory -j6 test-common ts-type-tests test-wasm-node test-wasm-browser lib-typecheck test-yarnpnp
|
||||
|
||||
check-go-version:
|
||||
@go version | grep -F " go$(GO_VERSION) " || (echo 'Please install Go version $(GO_VERSION)' && false)
|
||||
@@ -47,7 +47,7 @@ check-go-version:
|
||||
# variable like this: "ESBUILD_RACE=-race make test". Or you can permanently
|
||||
# enable it by adding "export ESBUILD_RACE=-race" to your shell profile.
|
||||
test-go:
|
||||
go test $(ESBUILD_RACE) ./internal/... ./pkg/...
|
||||
go test $(ESBUILD_RACE) ./cmd/... ./internal/... ./pkg/...
|
||||
|
||||
vet-go:
|
||||
go vet ./cmd/... ./internal/... ./pkg/...
|
||||
@@ -76,7 +76,8 @@ go-compiler: go/$(GO_VERSION)
|
||||
# into other text, and a local development server that serves a read-only
|
||||
# view of your source code and is not intended for production use.
|
||||
go/$(GO_VERSION):
|
||||
mkdir -p go/$(GO_VERSION) && cd go/$(GO_VERSION) && curl -LO https://go.dev/dl/go$(GO_VERSION).src.tar.gz && tar xzf go$(GO_VERSION).src.tar.gz
|
||||
mkdir -p go/$(GO_VERSION) && touch go/go.mod # Disable the Go language server in this folder
|
||||
cd go/$(GO_VERSION) && curl -LO https://go.dev/dl/go$(GO_VERSION).src.tar.gz && tar xzf go$(GO_VERSION).src.tar.gz
|
||||
cd go/$(GO_VERSION)/go && grep -qF "ctxt.buildinfo()" src/cmd/link/internal/ld/main.go # Make sure this call is still there
|
||||
cd go/$(GO_VERSION)/go/src/cmd/link/internal/ld && sed "s/ctxt.buildinfo/\\/\\/ctxt.buildinfo/" main.go > temp && mv temp main.go
|
||||
cd go/$(GO_VERSION)/go/src && if uname | grep -qE "MINGW|WIN32|CYGWIN"; then ./make.bat; else ./make.bash; fi
|
||||
@@ -97,16 +98,6 @@ test-wasm-node: esbuild
|
||||
test-wasm-browser: platform-wasm | scripts/browser/node_modules
|
||||
cd scripts/browser && node browser-tests.js
|
||||
|
||||
test-deno: esbuild platform-deno
|
||||
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno test --allow-run --allow-env --allow-net --allow-read --allow-write --no-check scripts/deno-tests.js
|
||||
@echo '✅ deno tests passed' # I couldn't find a Deno API for telling when tests have failed, so I'm doing this here instead
|
||||
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno eval 'import { transform, stop } from "file://$(shell pwd)/deno/mod.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
|
||||
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno eval 'import { transform, stop } from "file://$(shell pwd)/deno/wasm.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
|
||||
ESBUILD_BINARY_PATH="$(shell pwd)/esbuild" deno run -A './deno/mod.js' # See: https://github.com/evanw/esbuild/pull/3917
|
||||
|
||||
test-deno-windows: esbuild platform-deno
|
||||
ESBUILD_BINARY_PATH=./esbuild.exe deno test --allow-run --allow-env --allow-net --allow-read --allow-write --no-check scripts/deno-tests.js
|
||||
|
||||
register-test: version-go | scripts/node_modules
|
||||
node scripts/esbuild.js npm/esbuild/package.json --version
|
||||
node scripts/register-test.js
|
||||
@@ -151,7 +142,7 @@ test-old-ts: | require/old-ts/node_modules
|
||||
node-unref-tests: | scripts/node_modules
|
||||
node scripts/node-unref-tests.js
|
||||
|
||||
lib-typecheck: lib-typecheck-node lib-typecheck-node-nolib lib-typecheck-deno
|
||||
lib-typecheck: lib-typecheck-node lib-typecheck-node-nolib
|
||||
|
||||
lib-typecheck-node: | lib/node_modules
|
||||
cd lib && node_modules/.bin/tsc -noEmit -p tsconfig.json
|
||||
@@ -159,14 +150,8 @@ lib-typecheck-node: | lib/node_modules
|
||||
lib-typecheck-node-nolib: | lib/node_modules
|
||||
cd lib && node_modules/.bin/tsc -noEmit -p tsconfig-nolib.json
|
||||
|
||||
lib-typecheck-deno: lib/deno/lib.deno.d.ts | lib/node_modules
|
||||
cd lib && node_modules/.bin/tsc -noEmit -p tsconfig-deno.json
|
||||
|
||||
lib/deno/lib.deno.d.ts:
|
||||
deno types > lib/deno/lib.deno.d.ts
|
||||
|
||||
# End-to-end tests
|
||||
test-e2e: test-e2e-npm test-e2e-pnpm test-e2e-yarn test-e2e-yarn-berry test-e2e-deno
|
||||
test-e2e: test-e2e-npm test-e2e-pnpm test-e2e-yarn test-e2e-yarn-berry
|
||||
|
||||
test-e2e-npm:
|
||||
# Test normal install
|
||||
@@ -295,10 +280,6 @@ test-e2e-yarn-berry:
|
||||
# Clean up
|
||||
rm -fr e2e-yb
|
||||
|
||||
test-e2e-deno:
|
||||
deno eval 'import { transform, stop } from "https://deno.land/x/esbuild@v$(ESBUILD_VERSION)/mod.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
|
||||
deno eval 'import { transform, stop } from "https://deno.land/x/esbuild@v$(ESBUILD_VERSION)/wasm.js"; console.log((await transform("1+2")).code); stop()' | grep "1 + 2;"
|
||||
|
||||
test-yarnpnp: platform-wasm
|
||||
node scripts/test-yarnpnp.js
|
||||
|
||||
@@ -319,7 +300,6 @@ version-go:
|
||||
platform-all: \
|
||||
platform-android-arm \
|
||||
platform-android-x64 \
|
||||
platform-deno \
|
||||
platform-neutral \
|
||||
platform-openharmony-arm64 \
|
||||
platform-wasi-preview1 \
|
||||
@@ -462,11 +442,6 @@ platform-wasm: esbuild go-compiler
|
||||
$(GO_COMPILER) "$(NODE)" scripts/esbuild.js ./esbuild --wasm
|
||||
@shasum -a 256 npm/esbuild-wasm/esbuild.wasm
|
||||
|
||||
platform-deno: platform-wasm go-compiler
|
||||
@echo
|
||||
@echo "# Build: deno"
|
||||
$(GO_COMPILER) "$(NODE)" scripts/esbuild.js ./esbuild --deno
|
||||
|
||||
publish-all: check-go-version
|
||||
# Make sure the npm directory is pristine (including .gitignored files) since it will be published
|
||||
rm -fr npm && git checkout npm
|
||||
|
||||
+4
-14
@@ -23,19 +23,6 @@ Pushing a change to [`version.txt`](./version.txt) causes the following:
|
||||
2. Create a tag for the release that looks like `v0.X.Y`
|
||||
3. Publish a [GitHub Release](https://github.com/evanw/esbuild/releases) containing the release notes in [`CHANGELOG.md`](./CHANGELOG.md)
|
||||
|
||||
- The [`release.yml`](https://github.com/esbuild/deno-esbuild/blob/main/.github/workflows/release.yml)
|
||||
workflow in the https://github.com/esbuild/deno-esbuild repo runs
|
||||
occasionally. On the next run, it will notice the version change and:
|
||||
|
||||
1. Clone this repo
|
||||
2. Run `make platform-deno`
|
||||
3. Commit and push the new contents of the `deno` folder to the `deno-esbuild` repo
|
||||
4. Create a tag for the release that looks like `v0.X.Y`
|
||||
5. Post an event to the https://api.deno.land/webhook/gh/esbuild webhook
|
||||
6. Deno will then add a new version to https://deno.land/x/esbuild
|
||||
|
||||
You can also manually trigger this workflow if you want it to happen immediately.
|
||||
|
||||
- The [`release.yml`](https://github.com/esbuild/esbuild.github.io/blob/main/.github/workflows/release.yml)
|
||||
workflow in the https://github.com/esbuild/esbuild.github.io repo runs
|
||||
occasionally. On the next run, it will notice the version change and:
|
||||
@@ -45,7 +32,10 @@ Pushing a change to [`version.txt`](./version.txt) causes the following:
|
||||
3. Commit and push these new scripts to the `gh-pages` branch of the `esbuild.github.io` repo
|
||||
4. GitHub Pages will then deploy these updates to https://esbuild.github.io/
|
||||
|
||||
You can also manually trigger this workflow if you want it to happen immediately.
|
||||
You can also manually trigger this workflow if you want it to happen
|
||||
immediately. Note that this workflow periodically disables itself due to
|
||||
inactivity so it's important to check that it's still running after a
|
||||
release.
|
||||
|
||||
## Adding a new package
|
||||
|
||||
|
||||
+26
-4
@@ -64,7 +64,7 @@ func (service *serviceType) createActiveBuild(key int) *activeBuild {
|
||||
activeBuild := &activeBuild{}
|
||||
service.activeBuilds[key] = activeBuild
|
||||
|
||||
// This pairs with "Done()" in "decRefCount"
|
||||
// This pairs with "Done()" in "destroyActiveBuild"
|
||||
service.keepAliveWaitGroup.Add(1)
|
||||
return activeBuild
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (service *serviceType) destroyActiveBuild(key int) {
|
||||
delete(service.activeBuilds, key)
|
||||
service.mutex.Unlock()
|
||||
|
||||
// This pairs with "Add()" in "trackActiveBuild"
|
||||
// This pairs with "Add()" in "createActiveBuild"
|
||||
service.keepAliveWaitGroup.Done()
|
||||
}
|
||||
|
||||
@@ -639,6 +639,12 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
|
||||
}
|
||||
|
||||
activeBuild := service.createActiveBuild(key)
|
||||
shouldDestroyActiveBuild := true
|
||||
defer func() {
|
||||
if shouldDestroyActiveBuild {
|
||||
service.destroyActiveBuild(key)
|
||||
}
|
||||
}()
|
||||
|
||||
hasOnEndCallbacks := false
|
||||
if plugins, ok := request["plugins"]; ok {
|
||||
@@ -769,6 +775,7 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
|
||||
// Keep the build alive until "dispose" has been called
|
||||
activeBuild.disposeWaitGroup.Add(1)
|
||||
activeBuild.ctx = ctx
|
||||
shouldDestroyActiveBuild = false
|
||||
|
||||
return encodePacket(packet{
|
||||
id: id,
|
||||
@@ -782,8 +789,6 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
|
||||
result := api.Build(options)
|
||||
response := resultToResponse(result)
|
||||
|
||||
service.destroyActiveBuild(key)
|
||||
|
||||
return encodePacket(packet{
|
||||
id: id,
|
||||
value: response,
|
||||
@@ -1240,6 +1245,23 @@ func (service *serviceType) handleFormatMessagesRequest(id uint32, request map[s
|
||||
if value, ok := request["terminalWidth"].(int); ok {
|
||||
options.TerminalWidth = value
|
||||
}
|
||||
if logStyle, ok := request["logStyle"].(string); ok {
|
||||
switch logStyle {
|
||||
case "default":
|
||||
options.LogStyle = api.LogStyleDefault
|
||||
case "clang":
|
||||
options.LogStyle = api.LogStyleClang
|
||||
case "visualstudio":
|
||||
options.LogStyle = api.LogStyleVisualStudio
|
||||
default:
|
||||
return encodePacket(packet{
|
||||
id: id,
|
||||
value: map[string]interface{}{
|
||||
"error": fmt.Sprintf("Invalid log style: %q", logStyle),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result := api.FormatMessages(msgs, options)
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/evanw/esbuild/internal/helpers"
|
||||
)
|
||||
|
||||
func TestHandleBuildRequestReleasesActiveBuildAfterError(t *testing.T) {
|
||||
absWorkingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
modifyRequest func(map[string]interface{})
|
||||
expectedError string
|
||||
expectedMessage string
|
||||
}{
|
||||
{
|
||||
name: "InvalidPluginRegExp",
|
||||
modifyRequest: func(request map[string]interface{}) {
|
||||
request["plugins"] = []interface{}{map[string]interface{}{
|
||||
"name": "test",
|
||||
"onEnd": false,
|
||||
"onResolve": []interface{}{map[string]interface{}{
|
||||
"id": 0,
|
||||
"filter": "x(?=y)",
|
||||
"namespace": "",
|
||||
}},
|
||||
"onLoad": []interface{}{},
|
||||
}}
|
||||
},
|
||||
expectedError: "[test] \"onResolve\" filter is not a valid Go regular expression: \"x(?=y)\"",
|
||||
},
|
||||
{
|
||||
name: "InvalidContextOptions",
|
||||
modifyRequest: func(request map[string]interface{}) {
|
||||
request["context"] = true
|
||||
request["flags"] = []interface{}{"--external:test"}
|
||||
},
|
||||
expectedMessage: "Cannot use \"external\" without \"bundle\"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
service := serviceType{
|
||||
activeBuilds: make(map[int]*activeBuild),
|
||||
keepAliveWaitGroup: helpers.MakeThreadSafeWaitGroup(),
|
||||
}
|
||||
request := map[string]interface{}{
|
||||
"context": false,
|
||||
"key": 0,
|
||||
"write": false,
|
||||
"entries": []interface{}{},
|
||||
"flags": []interface{}{},
|
||||
"absWorkingDir": absWorkingDir,
|
||||
"nodePaths": []interface{}{},
|
||||
}
|
||||
test.modifyRequest(request)
|
||||
|
||||
encoded := service.handleBuildRequest(0, request)
|
||||
bytes, _, ok := readLengthPrefixedSlice(encoded)
|
||||
if !ok {
|
||||
t.Fatal("Invalid encoded response")
|
||||
}
|
||||
response, ok := decodePacket(bytes)
|
||||
if !ok {
|
||||
t.Fatal("Invalid response packet")
|
||||
}
|
||||
value := response.value.(map[string]interface{})
|
||||
if test.expectedError != "" && value["error"] != test.expectedError {
|
||||
t.Fatalf("Expected error %q, got %q", test.expectedError, value["error"])
|
||||
}
|
||||
if test.expectedMessage != "" {
|
||||
errors := value["errors"].([]interface{})
|
||||
if len(errors) != 1 || errors[0].(map[string]interface{})["text"] != test.expectedMessage {
|
||||
t.Fatalf("Expected message %q, got %v", test.expectedMessage, errors)
|
||||
}
|
||||
}
|
||||
|
||||
if service.getActiveBuild(0) != nil {
|
||||
t.Fatal("Active build was not released")
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
service.keepAliveWaitGroup.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Keep-alive wait group did not reach zero")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
const esbuildVersion = "0.28.1"
|
||||
const esbuildVersion = "0.28.2"
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.26.4
|
||||
1.26.5
|
||||
|
||||
+68
-26
@@ -116,7 +116,8 @@ type globResolveResult struct {
|
||||
|
||||
type tlaCheck struct {
|
||||
parent ast.Index32
|
||||
depth uint32
|
||||
depth ast.Index32
|
||||
pass uint32
|
||||
importRecordIndex uint32
|
||||
}
|
||||
|
||||
@@ -2804,19 +2805,24 @@ func (s *scanner) processScannedFiles(entryPointMeta []graph.EntryPoint) []scann
|
||||
AbsPath: s.fs.Join(s.options.AbsOutputDir, relPath),
|
||||
Contents: bytes,
|
||||
JSONMetadataChunk: jsonMetadataChunk,
|
||||
CanBeMerged: true,
|
||||
}}
|
||||
}
|
||||
|
||||
s.results[sourceIndex] = result
|
||||
}
|
||||
|
||||
// Traverse the graph to check top-level await
|
||||
if s.iterativelyValidateTLA() {
|
||||
s.reportInvalidTLA()
|
||||
}
|
||||
|
||||
// The linker operates on an array of files, so construct that now. This
|
||||
// can't be constructed earlier because we generate new parse results for
|
||||
// JavaScript stub files for CSS imports above.
|
||||
files := make([]scannerFile, len(s.results))
|
||||
for sourceIndex := range s.results {
|
||||
if result := &s.results[sourceIndex]; result.ok {
|
||||
s.validateTLA(uint32(sourceIndex))
|
||||
files[sourceIndex] = result.file
|
||||
}
|
||||
}
|
||||
@@ -2824,33 +2830,69 @@ func (s *scanner) processScannedFiles(entryPointMeta []graph.EntryPoint) []scann
|
||||
return files
|
||||
}
|
||||
|
||||
func (s *scanner) validateTLA(sourceIndex uint32) tlaCheck {
|
||||
func (s *scanner) iterativelyValidateTLA() bool {
|
||||
pass := uint32(1)
|
||||
hasTLA := false
|
||||
|
||||
// Iterate until a fixed point has been reached to handle graph cycles
|
||||
for {
|
||||
didChange := false
|
||||
for sourceIndex := range s.results {
|
||||
s.recursivelyValidateTLA(uint32(sourceIndex), pass, &didChange)
|
||||
}
|
||||
if !didChange {
|
||||
return hasTLA
|
||||
}
|
||||
pass++
|
||||
hasTLA = true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scanner) recursivelyValidateTLA(sourceIndex uint32, pass uint32, didChange *bool) tlaCheck {
|
||||
result := &s.results[sourceIndex]
|
||||
|
||||
if result.ok && result.tlaCheck.depth == 0 {
|
||||
// Use a "pass" integer instead of a separate "visited" set
|
||||
if result.ok && result.tlaCheck.pass != pass {
|
||||
result.tlaCheck.pass = pass
|
||||
|
||||
if repr, ok := result.file.inputFile.Repr.(*graph.JSRepr); ok {
|
||||
result.tlaCheck.depth = 1
|
||||
if repr.AST.LiveTopLevelAwaitKeyword.Len > 0 {
|
||||
// If this module contains top-level await, set its parent to itself
|
||||
if repr.AST.LiveTopLevelAwaitKeyword.Len > 0 && result.tlaCheck.parent.GetIndex() != sourceIndex {
|
||||
result.tlaCheck.parent = ast.MakeIndex32(sourceIndex)
|
||||
result.tlaCheck.depth = ast.MakeIndex32(1)
|
||||
*didChange = true
|
||||
}
|
||||
|
||||
// Check all import statements and require calls (only import statements are valid)
|
||||
for importRecordIndex, record := range repr.AST.ImportRecords {
|
||||
if record.SourceIndex.IsValid() && (record.Kind == ast.ImportRequire || record.Kind == ast.ImportStmt) {
|
||||
parent := s.validateTLA(record.SourceIndex.GetIndex())
|
||||
if !parent.parent.IsValid() {
|
||||
continue
|
||||
}
|
||||
parent := s.recursivelyValidateTLA(record.SourceIndex.GetIndex(), pass, didChange)
|
||||
|
||||
// Follow any import chains
|
||||
if record.Kind == ast.ImportStmt && (!result.tlaCheck.parent.IsValid() || parent.depth < result.tlaCheck.depth) {
|
||||
result.tlaCheck.depth = parent.depth + 1
|
||||
// Track the shallowest top-level await parent (used to report invalid import chains later on)
|
||||
if record.Kind == ast.ImportStmt && parent.depth.GetIndex() < result.tlaCheck.depth.GetIndex()-1 {
|
||||
result.tlaCheck.parent = record.SourceIndex
|
||||
result.tlaCheck.depth = ast.MakeIndex32(parent.depth.GetIndex() + 1)
|
||||
result.tlaCheck.importRecordIndex = uint32(importRecordIndex)
|
||||
*didChange = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.tlaCheck
|
||||
}
|
||||
|
||||
func (s *scanner) reportInvalidTLA() {
|
||||
for sourceIndex := range s.results {
|
||||
result := &s.results[sourceIndex]
|
||||
|
||||
if result.ok && result.tlaCheck.parent.IsValid() {
|
||||
if repr, ok := result.file.inputFile.Repr.(*graph.JSRepr); ok {
|
||||
for _, record := range repr.AST.ImportRecords {
|
||||
// Require of a top-level await chain is forbidden
|
||||
if record.Kind == ast.ImportRequire {
|
||||
if record.Kind == ast.ImportRequire && record.SourceIndex.IsValid() && s.results[record.SourceIndex.GetIndex()].tlaCheck.parent.IsValid() {
|
||||
var notes []logger.MsgData
|
||||
var tlaPrettyPaths logger.PrettyPaths
|
||||
otherSourceIndex := record.SourceIndex.GetIndex()
|
||||
@@ -2899,18 +2941,14 @@ func (s *scanner) validateTLA(sourceIndex uint32) tlaCheck {
|
||||
s.log.AddErrorWithNotes(&tracker, record.Range, text, notes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that if we wrap this module in a closure, the closure is also
|
||||
// async. This happens when you call "import()" on this module and code
|
||||
// splitting is off.
|
||||
if result.tlaCheck.parent.IsValid() {
|
||||
// Make sure that if we wrap this module in a closure, the closure is also
|
||||
// async. This happens when you call "import()" on this module and code
|
||||
// splitting is off.
|
||||
repr.Meta.IsAsyncOrHasAsyncDependency = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.tlaCheck
|
||||
}
|
||||
|
||||
func DefaultExtensionToLoaderMap() map[string]config.Loader {
|
||||
@@ -3128,22 +3166,25 @@ func (b *Bundle) Compile(log logger.Log, timer *helpers.Timer, mangleCache map[s
|
||||
// Make an exception for files that have identical contents. In that case
|
||||
// the duplicate is just silently filtered out. This can happen with the
|
||||
// "file" loader, for example.
|
||||
outputFileMap := make(map[string][]byte)
|
||||
outputFileMap := make(map[string]graph.OutputFile)
|
||||
end := 0
|
||||
for _, outputFile := range outputFiles {
|
||||
absPathKey := canonicalFileSystemPathForWindows(outputFile.AbsPath)
|
||||
contents, ok := outputFileMap[absPathKey]
|
||||
existingFile, ok := outputFileMap[absPathKey]
|
||||
|
||||
// If this isn't a duplicate, keep the output file
|
||||
if !ok {
|
||||
outputFileMap[absPathKey] = outputFile.Contents
|
||||
outputFileMap[absPathKey] = outputFile
|
||||
outputFiles[end] = outputFile
|
||||
end++
|
||||
continue
|
||||
}
|
||||
|
||||
// If the names and contents are both the same, only keep the first one
|
||||
if bytes.Equal(contents, outputFile.Contents) {
|
||||
// If the names and contents are both the same, only keep the first one.
|
||||
// We only do this for assets, not for code, as two identical code files
|
||||
// need unique identity as they might need to have their own separate
|
||||
// copies of internal state. See https://github.com/evanw/esbuild/issues/4411
|
||||
if existingFile.CanBeMerged && outputFile.CanBeMerged && bytes.Equal(existingFile.Contents, outputFile.Contents) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -3152,6 +3193,7 @@ func (b *Bundle) Compile(log logger.Log, timer *helpers.Timer, mangleCache map[s
|
||||
if relPath, ok := b.fs.Rel(b.fs.Cwd(), outputPath); ok {
|
||||
outputPath = relPath
|
||||
}
|
||||
outputPath = strings.ReplaceAll(outputPath, "\\", "/")
|
||||
log.AddError(nil, logger.Range{}, "Two output files share the same path but have different contents: "+outputPath)
|
||||
}
|
||||
outputFiles = outputFiles[:end]
|
||||
|
||||
@@ -3131,6 +3131,47 @@ func TestNoOverwriteInputFileError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/evanw/esbuild/issues/4411
|
||||
func TestDuplicateChunkNameIssue4411(t *testing.T) {
|
||||
default_suite.expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
"/entry-a1.js": `
|
||||
import { add } from "./shared-a/shared.js"
|
||||
export const addA1 = () => add("a1")
|
||||
`,
|
||||
"/entry-a2.js": `
|
||||
import { add } from "./shared-a/shared.js"
|
||||
export const addA2 = () => add("a2")
|
||||
`,
|
||||
"/entry-b1.js": `
|
||||
import { add } from "./shared-b/shared.js"
|
||||
export const addB1 = () => add("b1")
|
||||
`,
|
||||
"/entry-b2.js": `
|
||||
import { add } from "./shared-b/shared.js"
|
||||
export const addB2 = () => add("b2")
|
||||
`,
|
||||
"/shared-a/shared.js": `
|
||||
const cache = []
|
||||
export const add = str => cache.push(str)
|
||||
`,
|
||||
"/shared-b/shared.js": `
|
||||
const cache = []
|
||||
export const add = str => cache.push(str)
|
||||
`,
|
||||
},
|
||||
entryPaths: []string{"/entry-a1.js", "/entry-a2.js", "/entry-b1.js", "/entry-b2.js"},
|
||||
options: config.Options{
|
||||
Mode: config.ModeBundle,
|
||||
AbsOutputDir: "/out",
|
||||
CodeSplitting: true,
|
||||
ChunkPathTemplate: []config.PathTemplate{{Placeholder: config.NamePlaceholder}},
|
||||
},
|
||||
expectedCompileLog: `ERROR: Two output files share the same path but have different contents: out/chunk.js
|
||||
`,
|
||||
})
|
||||
}
|
||||
|
||||
func TestDuplicateEntryPoint(t *testing.T) {
|
||||
default_suite.expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
@@ -3141,8 +3182,10 @@ func TestDuplicateEntryPoint(t *testing.T) {
|
||||
entryPaths: []string{"/entry.js", "/entry.js"},
|
||||
options: config.Options{
|
||||
Mode: config.ModeBundle,
|
||||
AbsOutputDir: "/out.js",
|
||||
AbsOutputDir: "/out",
|
||||
},
|
||||
expectedCompileLog: `ERROR: Two output files share the same path but have different contents: out/entry.js
|
||||
`,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3156,7 +3199,7 @@ func TestRelativeEntryPointError(t *testing.T) {
|
||||
entryPaths: []string{"entry"},
|
||||
options: config.Options{
|
||||
Mode: config.ModeBundle,
|
||||
AbsOutputDir: "/out.js",
|
||||
AbsOutputDir: "/out",
|
||||
},
|
||||
expectedScanLog: `ERROR: Could not resolve "entry"
|
||||
NOTE: Use the relative path "./entry" to reference the file "entry.js". Without the leading "./", the path "entry" is being interpreted as a package path instead.
|
||||
@@ -3174,11 +3217,29 @@ func TestMultipleEntryPointsSameNameCollision(t *testing.T) {
|
||||
entryPaths: []string{"/a/entry.js", "/b/entry.js"},
|
||||
options: config.Options{
|
||||
Mode: config.ModeBundle,
|
||||
AbsOutputDir: "/out/",
|
||||
AbsOutputDir: "/out",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultipleEntryPointsSameNameCollisionError(t *testing.T) {
|
||||
default_suite.expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
"/a/entry.js": `import {foo} from '../common.js'; console.log(foo)`,
|
||||
"/b/entry.js": `import {foo} from '../common.js'; console.log(foo)`,
|
||||
"/common.js": `export let foo = 123`,
|
||||
},
|
||||
entryPaths: []string{"/a/entry.js", "/b/entry.js"},
|
||||
options: config.Options{
|
||||
Mode: config.ModeBundle,
|
||||
AbsOutputDir: "/out",
|
||||
EntryPathTemplate: []config.PathTemplate{{Placeholder: config.NamePlaceholder}},
|
||||
},
|
||||
expectedCompileLog: `ERROR: Two output files share the same path but have different contents: out/entry.js
|
||||
`,
|
||||
})
|
||||
}
|
||||
|
||||
func TestReExportCommonJSAsES6(t *testing.T) {
|
||||
default_suite.expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
@@ -4377,6 +4438,39 @@ func TestTopLevelAwaitAllowedImportWithSplitting(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/evanw/esbuild/issues/4498
|
||||
func TestTopLevelAwaitCyclicDependenciesIssue4498(t *testing.T) {
|
||||
default_suite.expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
"/entry.mjs": `
|
||||
await import("./main.mjs");
|
||||
`,
|
||||
"/main.mjs": `
|
||||
import { a } from "./a.mjs";
|
||||
console.log(a());
|
||||
`,
|
||||
"/a.mjs": `
|
||||
import { b } from "./b.mjs";
|
||||
import { tla } from "./dep.mjs";
|
||||
export function a() { return b() + tla; }
|
||||
`,
|
||||
"/b.mjs": `
|
||||
import { a } from "./a.mjs";
|
||||
export function b() { return typeof a; }
|
||||
`,
|
||||
"/dep.mjs": `
|
||||
export const tla = await Promise.resolve("x");
|
||||
`,
|
||||
},
|
||||
entryPaths: []string{"/entry.mjs"},
|
||||
options: config.Options{
|
||||
Mode: config.ModeBundle,
|
||||
OutputFormat: config.FormatESModule,
|
||||
AbsOutputDir: "/out",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssignToImport(t *testing.T) {
|
||||
default_suite.expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
|
||||
@@ -3064,3 +3064,29 @@ func TestParameterPropsUseDefineForClassFieldsTrueLowered(t *testing.T) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/4507
|
||||
func TestTreeShakingImportAliasIssue4507(t *testing.T) {
|
||||
ts_suite.expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
"/entry.ts": `
|
||||
import Base from './dep.js';
|
||||
import Alias = Base.SomeType;
|
||||
|
||||
export function make() {
|
||||
return new Base();
|
||||
}
|
||||
`,
|
||||
"/dep.js": `
|
||||
export default class Base {
|
||||
static hello() { return 'dep code is present'; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
entryPaths: []string{"/entry.ts"},
|
||||
options: config.Options{
|
||||
Mode: config.ModeBundle,
|
||||
AbsOutputFile: "/out.js",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1156,12 +1156,6 @@ var require_index = __commonJS({
|
||||
var import__ = __toESM(require_index());
|
||||
console.log(import__.x);
|
||||
|
||||
================================================================================
|
||||
TestDuplicateEntryPoint
|
||||
---------- /out.js/entry.js ----------
|
||||
// entry.js
|
||||
console.log(123);
|
||||
|
||||
================================================================================
|
||||
TestDuplicatePropertyWarning
|
||||
---------- /out/entry.js ----------
|
||||
@@ -6897,6 +6891,50 @@ TestTopLevelAwaitCJSDeadBranch
|
||||
if (false) foo;
|
||||
if (false) for (foo of bar) ;
|
||||
|
||||
================================================================================
|
||||
TestTopLevelAwaitCyclicDependenciesIssue4498
|
||||
---------- /out/entry.js ----------
|
||||
// b.mjs
|
||||
function b() {
|
||||
return typeof a;
|
||||
}
|
||||
var init_b = __esm({
|
||||
async "b.mjs"() {
|
||||
await init_a();
|
||||
}
|
||||
});
|
||||
|
||||
// dep.mjs
|
||||
var tla;
|
||||
var init_dep = __esm({
|
||||
async "dep.mjs"() {
|
||||
tla = await Promise.resolve("x");
|
||||
}
|
||||
});
|
||||
|
||||
// a.mjs
|
||||
function a() {
|
||||
return b() + tla;
|
||||
}
|
||||
var init_a = __esm({
|
||||
async "a.mjs"() {
|
||||
await init_b();
|
||||
await init_dep();
|
||||
}
|
||||
});
|
||||
|
||||
// main.mjs
|
||||
var main_exports = {};
|
||||
var init_main = __esm({
|
||||
async "main.mjs"() {
|
||||
await init_a();
|
||||
console.log(a());
|
||||
}
|
||||
});
|
||||
|
||||
// entry.mjs
|
||||
await init_main().then(() => main_exports);
|
||||
|
||||
================================================================================
|
||||
TestTopLevelAwaitESM
|
||||
---------- /out.js ----------
|
||||
|
||||
@@ -2251,3 +2251,21 @@ if (nested) {
|
||||
new Bar(bar2(objBar));
|
||||
}
|
||||
var bar;
|
||||
|
||||
================================================================================
|
||||
TestTreeShakingImportAliasIssue4507
|
||||
---------- /out.js ----------
|
||||
// dep.js
|
||||
var Base = class {
|
||||
static hello() {
|
||||
return "dep code is present";
|
||||
}
|
||||
};
|
||||
|
||||
// entry.ts
|
||||
function make() {
|
||||
return new Base();
|
||||
}
|
||||
export {
|
||||
make
|
||||
};
|
||||
|
||||
@@ -57,6 +57,92 @@ func compareVersions(a v, b Semver) int {
|
||||
return diff
|
||||
}
|
||||
|
||||
func CompareSemver(a Semver, b Semver) int {
|
||||
an := len(a.Parts)
|
||||
bn := len(b.Parts)
|
||||
|
||||
n := an
|
||||
if bn > n {
|
||||
n = bn
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
ai := 0
|
||||
bi := 0
|
||||
if i < an {
|
||||
ai = a.Parts[i]
|
||||
}
|
||||
if i < bn {
|
||||
bi = b.Parts[i]
|
||||
}
|
||||
if ai != bi {
|
||||
return ai - bi
|
||||
}
|
||||
}
|
||||
|
||||
if (a.PreRelease != "") != (b.PreRelease != "") {
|
||||
return len(b.PreRelease) - len(a.PreRelease)
|
||||
}
|
||||
|
||||
a_tail := strings.TrimPrefix(a.PreRelease, "-")
|
||||
b_tail := strings.TrimPrefix(b.PreRelease, "-")
|
||||
|
||||
for a_tail != "" && b_tail != "" {
|
||||
var a_head, b_head string
|
||||
a_head, a_tail = splitOffNextPreReleasePart(a_tail)
|
||||
b_head, b_tail = splitOffNextPreReleasePart(b_tail)
|
||||
if a_head == b_head {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for numbers
|
||||
a_num, a_isnum := preReleasePartToNumber(a_head)
|
||||
b_num, b_isnum := preReleasePartToNumber(b_head)
|
||||
if !a_isnum && !b_isnum {
|
||||
// Lexicographic comparison for non-numbers
|
||||
if a_head < b_head {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
} else if a_isnum && b_isnum {
|
||||
// Numeric comparison for numbers
|
||||
if a_num != b_num {
|
||||
return a_num - b_num
|
||||
} else {
|
||||
// Compare lengths for different text but equal numbers (e.g. "0" vs. "00")
|
||||
return len(a_head) - len(b_head)
|
||||
}
|
||||
} else {
|
||||
// One is a number and the other isn't
|
||||
if a_isnum {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len(a_tail) - len(b_tail)
|
||||
}
|
||||
|
||||
func splitOffNextPreReleasePart(text string) (head string, tail string) {
|
||||
if i := strings.IndexByte(text, '.'); i != -1 {
|
||||
return text[:i], text[i+1:]
|
||||
}
|
||||
return text, ""
|
||||
}
|
||||
|
||||
func preReleasePartToNumber(text string) (int, bool) {
|
||||
for _, c := range text {
|
||||
if c < '0' || c > '9' {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
n, err := strconv.Atoi(text)
|
||||
return n, err == nil
|
||||
}
|
||||
|
||||
// The start is inclusive and the end is exclusive
|
||||
type versionRange struct {
|
||||
start v
|
||||
|
||||
@@ -61,3 +61,70 @@ func TestCompareVersions(t *testing.T) {
|
||||
check(v{1, 2, 3}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '>')
|
||||
check(v{1, 2, 2}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '<')
|
||||
}
|
||||
|
||||
func TestCompareSemver(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check := func(a Semver, b Semver, expected rune) {
|
||||
t.Helper()
|
||||
|
||||
at := a.String()
|
||||
bt := b.String()
|
||||
|
||||
t.Run(fmt.Sprintf("%q ? %q", at, bt), func(t *testing.T) {
|
||||
observed := '='
|
||||
if result := CompareSemver(a, b); result < 0 {
|
||||
observed = '<'
|
||||
} else if result > 0 {
|
||||
observed = '>'
|
||||
}
|
||||
if observed != expected {
|
||||
test.AssertEqual(t, fmt.Sprintf("%c", observed), fmt.Sprintf("%c", expected))
|
||||
}
|
||||
})
|
||||
|
||||
// Automatically generate reverse comparison cases to cut down on test count
|
||||
if expected == '<' {
|
||||
t.Run(fmt.Sprintf("%q ? %q", bt, at), func(t *testing.T) {
|
||||
observed := '='
|
||||
if result := CompareSemver(b, a); result < 0 {
|
||||
observed = '<'
|
||||
} else if result > 0 {
|
||||
observed = '>'
|
||||
}
|
||||
if observed != '>' {
|
||||
test.AssertEqual(t, fmt.Sprintf("%c", observed), ">")
|
||||
}
|
||||
})
|
||||
} else if expected == '>' {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
check(Semver{}, Semver{}, '=')
|
||||
check(Semver{Parts: []int{0}}, Semver{}, '=')
|
||||
check(Semver{}, Semver{Parts: []int{0}}, '=')
|
||||
check(Semver{Parts: []int{0}}, Semver{Parts: []int{0}}, '=')
|
||||
check(Semver{Parts: []int{0, 0}}, Semver{Parts: []int{0}}, '=')
|
||||
check(Semver{Parts: []int{0}}, Semver{Parts: []int{0}}, '=')
|
||||
check(Semver{Parts: []int{0, 0}}, Semver{Parts: []int{0, 0}}, '=')
|
||||
check(Semver{Parts: []int{1}, PreRelease: "-alpha"}, Semver{Parts: []int{1}, PreRelease: "-alpha"}, '=')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha"}, Semver{Parts: []int{1}, PreRelease: "-alpha"}, '=')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha"}, Semver{Parts: []int{1, 0, 0}, PreRelease: "-alpha"}, '=')
|
||||
|
||||
check(Semver{}, Semver{Parts: []int{1}}, '<')
|
||||
check(Semver{}, Semver{Parts: []int{0, 1}}, '<')
|
||||
check(Semver{Parts: []int{0}}, Semver{Parts: []int{1}}, '<')
|
||||
check(Semver{Parts: []int{0, 1}}, Semver{Parts: []int{1}}, '<')
|
||||
check(Semver{Parts: []int{0, 1}}, Semver{Parts: []int{1, 0}}, '<')
|
||||
check(Semver{Parts: []int{2, 0, 1}}, Semver{Parts: []int{2, 1, 0}}, '<')
|
||||
check(Semver{Parts: []int{2, 0, 1}}, Semver{Parts: []int{2, 0, 2}}, '<')
|
||||
check(Semver{Parts: []int{1}, PreRelease: "-alpha"}, Semver{Parts: []int{1}}, '<')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha"}, Semver{Parts: []int{1}, PreRelease: "-alpha.1"}, '<')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha.1"}, Semver{Parts: []int{1}, PreRelease: "-alpha.beta"}, '<')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha.1"}, Semver{Parts: []int{1}, PreRelease: "-alpha.11"}, '<')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha.1"}, Semver{Parts: []int{1}, PreRelease: "-alpha.02"}, '<')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha.1"}, Semver{Parts: []int{1}, PreRelease: "-alpha.01"}, '<')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha.01"}, Semver{Parts: []int{1}, PreRelease: "-alpha.2"}, '<')
|
||||
check(Semver{Parts: []int{1, 0}, PreRelease: "-alpha.08"}, Semver{Parts: []int{1}, PreRelease: "-alpha.011"}, '<')
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ var knownGlobals = [][]string{
|
||||
{"Math", "sin"},
|
||||
{"Math", "sinh"},
|
||||
{"Math", "sqrt"},
|
||||
{"Math", "sumPrecise"},
|
||||
{"Math", "tan"},
|
||||
{"Math", "tanh"},
|
||||
{"Math", "trunc"},
|
||||
@@ -166,7 +167,9 @@ var knownGlobals = [][]string{
|
||||
|
||||
// JSON: Static methods
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON#static_methods
|
||||
{"JSON", "isRawJSON"},
|
||||
{"JSON", "parse"},
|
||||
{"JSON", "rawJSON"},
|
||||
{"JSON", "stringify"},
|
||||
|
||||
// TypedArray: Static methods
|
||||
|
||||
@@ -437,7 +437,7 @@ func gamut_mapping_xyz_to_srgb(x F64, y F64, z F64) (F64, F64, F64) {
|
||||
}
|
||||
|
||||
clipped_r, clipped_g, clipped_b := clip(r), clip(g), clip(b)
|
||||
L1, a1, b1 := srgb_to_oklab(clipped_r, clipped_b, clipped_g)
|
||||
L1, a1, b1 := srgb_to_oklab(clipped_r, clipped_g, clipped_b)
|
||||
L2, a2, b2 := srgb_to_oklab(r, g, b)
|
||||
E := delta_eok(L1, a1, b1, L2, a2, b2)
|
||||
if E.Value() < JND {
|
||||
|
||||
@@ -2309,7 +2309,50 @@ func (p *parser) scanForEndOfRule() (endOfRuleScan, int) {
|
||||
var initialStack [4]css_lexer.T
|
||||
stack := initialStack[:0]
|
||||
|
||||
// Small state machine to recognize "--foo:"
|
||||
const (
|
||||
customPropIdent = iota
|
||||
customPropColon
|
||||
customPropAccept
|
||||
customPropReject
|
||||
)
|
||||
customProp := customPropIdent
|
||||
|
||||
for i, t := range p.tokens[p.index:] {
|
||||
// People sometimes use CSS custom properties to store JSON. This is
|
||||
// problematic because the "{...}" braces look like a CSS block unless
|
||||
// this edge case is special-cased. See the (quite long) discussion on
|
||||
// https://github.com/w3c/csswg-drafts/issues/7961 for more information.
|
||||
//
|
||||
// I couldn't find any actual specification for how this is supposed to
|
||||
// work. Just a lot of discussion on GitHub. I did find this text, from
|
||||
// https://drafts.csswg.org/css-syntax/#consume-a-declaration:
|
||||
//
|
||||
// Otherwise, if decl’s value contains a top-level simple block with
|
||||
// an associated token of <{-token>, and also contains any other non-
|
||||
// <whitespace-token> value, return nothing. (That is, a top-level {}-
|
||||
// block is only allowed as the entire value of a non-custom property.)
|
||||
//
|
||||
// However, current browsers appear to accept custom properties with a
|
||||
// {}-block contained anywhere inside them, not just as the entire value.
|
||||
// So that's what I'm attempting to do here.
|
||||
if customProp < customPropAccept {
|
||||
switch customProp {
|
||||
case customPropIdent:
|
||||
if t.Kind == css_lexer.TIdent && strings.HasPrefix(p.source.Contents[t.Range.Loc.Start:], "--") {
|
||||
customProp = customPropColon
|
||||
} else if t.Kind != css_lexer.TWhitespace {
|
||||
customProp = customPropReject
|
||||
}
|
||||
case customPropColon:
|
||||
if t.Kind == css_lexer.TColon {
|
||||
customProp = customPropAccept
|
||||
} else if t.Kind != css_lexer.TWhitespace {
|
||||
customProp = customPropReject
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch t.Kind {
|
||||
case css_lexer.TSemicolon:
|
||||
if len(stack) == 0 {
|
||||
@@ -2323,7 +2366,7 @@ func (p *parser) scanForEndOfRule() (endOfRuleScan, int) {
|
||||
stack = append(stack, css_lexer.TCloseBracket)
|
||||
|
||||
case css_lexer.TOpenBrace:
|
||||
if len(stack) == 0 {
|
||||
if len(stack) == 0 && customProp != customPropAccept {
|
||||
return endOfRuleOpenBrace, p.index + i
|
||||
}
|
||||
stack = append(stack, css_lexer.TCloseBrace)
|
||||
|
||||
@@ -252,8 +252,17 @@ const (
|
||||
func analyzeLeadingAmpersand(sel css_ast.ComplexSelector, isDeclarationContext bool) leadingAmpersand {
|
||||
if len(sel.Selectors) > 1 {
|
||||
if first := sel.Selectors[0]; first.IsSingleAmpersand() {
|
||||
if second := sel.Selectors[1]; second.Combinator.Byte == 0 && len(second.NestingSelectorLocs) > 0 {
|
||||
second := sel.Selectors[1]
|
||||
rest := css_ast.ComplexSelector{Selectors: sel.Selectors[1:]}
|
||||
if second.Combinator.Byte == 0 && rest.ContainsNestingCombinator() {
|
||||
// ".foo { & &.bar {} }" => ".foo { & &.bar {} }"
|
||||
// ".foo { & .bar:not(& .baz) {} }" => ".foo { & .bar:not(& .baz) {} }"
|
||||
|
||||
// The specification says: "If a selector in the <relative-selector-list>
|
||||
// does not start with a combinator but does contain the nesting selector,
|
||||
// it is interpreted as a non-relative selector." So we don't want to
|
||||
// remove the leading "&" here as that would change the meaning to a
|
||||
// non-relative selector. See: https://www.w3.org/TR/css-nesting-1/
|
||||
} else if second.Combinator.Byte != 0 || second.TypeSelector == nil || !isDeclarationContext {
|
||||
// "& + div {}" => "+ div {}"
|
||||
// "& div {}" => "div {}"
|
||||
@@ -264,6 +273,30 @@ func analyzeLeadingAmpersand(sel css_ast.ComplexSelector, isDeclarationContext b
|
||||
} else {
|
||||
// ".foo { & div {} }"
|
||||
// ".foo { .bar, & div {} }" => ".foo { .bar, div {} }"
|
||||
|
||||
// The first iteration of CSS nesting didn't support an implicit "&" if
|
||||
// the selector started with an identifier. This was done to avoid
|
||||
// confusion with declarations in the parser, as the parser would
|
||||
// otherwise need unbounded lookahead to distinguish between the two.
|
||||
// Consider the following:
|
||||
//
|
||||
// span {
|
||||
// color:blue;
|
||||
// div:hover span {}
|
||||
// }
|
||||
//
|
||||
// The "color:blue" is a declaration while "div:hover" is actually a
|
||||
// relative selector in a nested style rule. The parser would need to
|
||||
// scan to the ";" or "{" to distinguish between the two cases.
|
||||
// Generally you want to avoid parsers with unbounded lookahead.
|
||||
//
|
||||
// However, this edge case is commonly encountered and it's confusing
|
||||
// that it doesn't work. So the CSS working group later added unbounded
|
||||
// lookahead to fix it: https://github.com/w3c/csswg-drafts/issues/7961.
|
||||
//
|
||||
// We deliberately don't take advantage of that newest syntax addition
|
||||
// here so that our output still works in browsers before that newest
|
||||
// syntax addition. We still keep a leading "&" before identifiers.
|
||||
return canRemoveLeadingAmpersandIfNotFirst
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,6 +606,16 @@ func TestColorFunctions(t *testing.T) {
|
||||
expectPrintedLower(t, "a { color: oklch(0.953 16% 134) }", "a {\n color: #deface;\n}\n", "")
|
||||
expectPrintedLower(t, "a { color: oklch(0.953 0.064 0.372turn) }", "a {\n color: #deface;\n}\n", "")
|
||||
|
||||
// Check gamut mapping for out-of-gamut colors with distinct g/b channels.
|
||||
// This exercises the binary-search clipping path in "gamut_mapping_xyz_to_srgb",
|
||||
// which previously called "srgb_to_oklab" with the green and blue arguments
|
||||
// swapped. That bug was masked by every other test case above because they
|
||||
// either stay in-gamut or happen to have green ≈ blue, so the swap was a
|
||||
// no-op. These colors have green and blue far enough apart for the bug to
|
||||
// produce a visibly different (and less accurate) clipped color.
|
||||
expectPrintedLower(t, "a { color: oklch(0.65 0.3 50) }", "a {\n color: #e56300;\n color: oklch(0.65 0.3 50);\n}\n", "")
|
||||
expectPrintedLower(t, "a { color: oklch(0.8 0.3 200) }", "a {\n color: #00dae4;\n color: oklch(0.8 0.3 200);\n}\n", "")
|
||||
|
||||
// Test alpha
|
||||
expectPrintedLower(t, "a { color: color(srgb 0.87 0.98 0.807 / 0.5) }", "a {\n color: rgba(222, 250, 206, .5);\n}\n", "")
|
||||
expectPrintedLower(t, "a { color: lab(95.38 -15 18 / 0.5) }", "a {\n color: rgba(222, 250, 206, .5);\n}\n", "")
|
||||
@@ -822,9 +832,9 @@ func TestGradient(t *testing.T) {
|
||||
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(#ff0, color(display-p3 1 0 0));\n}\n", "")
|
||||
expectPrintedMinify(t, code, "a{background:"+gradient+"(yellow,color(display-p3 1 0 0))}", "")
|
||||
expectPrintedLowerUnsupported(t, compat.ColorFunctions, code,
|
||||
"a {\n background:\n "+gradient+"(\n #ffff00,\n #ffe971,\n #ffd472 25%,\n "+
|
||||
"#ffab5f,\n #ff7b45 75%,\n #ff5e38 87.5%,\n #ff5534,\n #ff4c30,\n "+
|
||||
"#ff412c,\n #ff0e0e);\n "+
|
||||
"a {\n background:\n "+gradient+"(\n #ffff00,\n #ffe96c,\n #ffd363 25%,\n "+
|
||||
"#ffa852,\n #ff7a42 75%,\n #ff562c 87.5%,\n #ff4e29,\n #ff4528,\n "+
|
||||
"#ff3a26,\n #ff0e0e);\n "+
|
||||
"background:\n "+gradient+"(\n #ffff00,\n color(xyz 0.734 0.805 0.111),\n "+
|
||||
"color(xyz 0.699 0.693 0.087) 25%,\n color(xyz 0.627 0.501 0.048),\n "+
|
||||
"color(xyz 0.556 0.348 0.019) 75%,\n color(xyz 0.521 0.284 0.009) 87.5%,\n "+
|
||||
@@ -1137,6 +1147,10 @@ func TestNestedSelector(t *testing.T) {
|
||||
expectPrintedMangle(t, "a { @media screen { & div { color: red } } }", "a {\n @media screen {\n & div {\n color: red;\n }\n }\n}\n", "")
|
||||
expectPrintedMangle(t, "a { :has(& b) { color: red } }", "a {\n :has(& b) {\n color: red;\n }\n}\n", "")
|
||||
expectPrintedMangle(t, "a { :has(& + b) { color: red } }", "a {\n :has(& + b) {\n color: red;\n }\n}\n", "")
|
||||
expectPrintedMangle(t, "a { & .b:not(& .c) { color: red } }", "a {\n & .b:not(& .c) {\n color: red;\n }\n}\n", "")
|
||||
expectPrintedMangle(t, "a { & .b:is(.c &) { color: red } }", "a {\n & .b:is(.c &) {\n color: red;\n }\n}\n", "")
|
||||
expectPrintedMangle(t, "a { & + .b:not(& .c) { color: red } }", "a {\n + .b:not(& .c) {\n color: red;\n }\n}\n", "")
|
||||
expectPrintedMangle(t, "a { & + .b:is(& .c) { color: red } }", "a {\n + .b:is(& .c) {\n color: red;\n }\n}\n", "")
|
||||
|
||||
// Reorder selectors to enable removing "&"
|
||||
expectPrintedMangle(t, "reorder { & first, .second { color: red } }", "reorder {\n .second,\n first {\n color: red;\n }\n}\n", "")
|
||||
@@ -1357,6 +1371,15 @@ func TestNestedSelector(t *testing.T) {
|
||||
"::placeholder {\n opacity: 0.5;\n}\n@layer base {\n ::placeholder {\n color: green;\n }\n}\n", "")
|
||||
}
|
||||
|
||||
func TestCustomPropertyContainingBraces(t *testing.T) {
|
||||
// CSS custom properties must be able to store JSON-like values
|
||||
// See: https://github.com/w3c/csswg-drafts/issues/7961
|
||||
expectPrinted(t, "div { --foo:hover{color:red}zoom:2; }", "div {\n --foo:hover{color:red}zoom:2;\n}\n", "")
|
||||
expectPrinted(t, "div { --foo:hover { color: red } zoom: 2; }", "div {\n --foo:hover { color: red } zoom: 2;\n}\n", "")
|
||||
expectPrinted(t, "div { --foo : hover { color : red } zoom : 2 ; }", "div {\n --foo: hover { color : red } zoom : 2 ;\n}\n", "")
|
||||
expectPrinted(t, "div { --foo + hover { color : red } zoom : 2 ; }", "div {\n --foo + hover {\n color: red;\n }\n zoom: 2;\n}\n", "")
|
||||
}
|
||||
|
||||
func TestBadQualifiedRules(t *testing.T) {
|
||||
expectPrinted(t, "$bad: rule;", "$bad: rule; {\n}\n", "<stdin>: WARNING: Unexpected \"$\"\n")
|
||||
expectPrinted(t, "$bad: rule; div { color: red }", "$bad: rule; div {\n color: red;\n}\n", "<stdin>: WARNING: Unexpected \"$\"\n")
|
||||
|
||||
@@ -277,19 +277,19 @@ func TestVerbatimWhitespace(t *testing.T) {
|
||||
expectPrintedMinify(t, "* { --x:[y ]; }", "*{--x:[y ]}")
|
||||
expectPrintedMinify(t, "* { --x:[ y]; }", "*{--x:[ y]}")
|
||||
|
||||
// Note: These cases now behave like qualified rules
|
||||
expectPrinted(t, "* { --x:{y}; }", "* {\n --x: {\n y;\n }\n}\n")
|
||||
expectPrinted(t, "* { --x:{y} ; }", "* {\n --x: {\n y;\n }\n}\n")
|
||||
expectPrinted(t, "* { --x: {y}; }", "* {\n --x: {\n y;\n }\n}\n")
|
||||
expectPrinted(t, "* { --x:{y }; }", "* {\n --x: {\n y;\n }\n}\n")
|
||||
expectPrinted(t, "* { --x:{ y}; }", "* {\n --x: {\n y;\n }\n}\n")
|
||||
// These cases temporarily behaved like qualified rules, but now they're back to being declarations
|
||||
expectPrinted(t, "* { --x:{y}; }", "* {\n --x:{y};\n}\n")
|
||||
expectPrinted(t, "* { --x:{y} ; }", "* {\n --x:{y} ;\n}\n")
|
||||
expectPrinted(t, "* { --x: {y}; }", "* {\n --x: {y};\n}\n")
|
||||
expectPrinted(t, "* { --x:{y }; }", "* {\n --x:{y };\n}\n")
|
||||
expectPrinted(t, "* { --x:{ y}; }", "* {\n --x:{ y};\n}\n")
|
||||
|
||||
// Note: These cases now behave like qualified rules
|
||||
// These cases temporarily behaved like qualified rules, but now they're back to being declarations
|
||||
expectPrintedMinify(t, "* { --x:{y}; }", "*{--x:{y}}")
|
||||
expectPrintedMinify(t, "* { --x:{y} ; }", "*{--x:{y}}")
|
||||
expectPrintedMinify(t, "* { --x: {y}; }", "*{--x:{y}}")
|
||||
expectPrintedMinify(t, "* { --x:{y }; }", "*{--x:{y}}")
|
||||
expectPrintedMinify(t, "* { --x:{ y}; }", "*{--x:{y}}")
|
||||
expectPrintedMinify(t, "* { --x:{y} ; }", "*{--x:{y} }")
|
||||
expectPrintedMinify(t, "* { --x: {y}; }", "*{--x: {y}}")
|
||||
expectPrintedMinify(t, "* { --x:{y }; }", "*{--x:{y }}")
|
||||
expectPrintedMinify(t, "* { --x:{ y}; }", "*{--x:{ y}}")
|
||||
|
||||
expectPrintedMinify(t, "@supports ( --x : y , z ) { a { color: red; } }", "@supports ( --x : y , z ){a{color:red}}")
|
||||
expectPrintedMinify(t, "@supports ( --x : ) { a { color: red; } }", "@supports ( --x : ){a{color:red}}")
|
||||
|
||||
@@ -43,6 +43,7 @@ type OutputFile struct {
|
||||
AbsPath string
|
||||
Contents []byte
|
||||
IsExecutable bool
|
||||
CanBeMerged bool
|
||||
}
|
||||
|
||||
type SideEffects struct {
|
||||
|
||||
@@ -24,6 +24,9 @@ var builtinTypesLower = map[string]string{
|
||||
".svg": "image/svg+xml",
|
||||
".webp": "image/webp",
|
||||
|
||||
// Audio
|
||||
".mp3": "audio/mpeg",
|
||||
|
||||
// Fonts
|
||||
".eot": "application/vnd.ms-fontobject",
|
||||
".otf": "font/otf",
|
||||
|
||||
@@ -540,9 +540,10 @@ type EUnary struct {
|
||||
}
|
||||
|
||||
type EBinary struct {
|
||||
Left Expr
|
||||
Right Expr
|
||||
Op OpCode
|
||||
Left Expr
|
||||
Right Expr
|
||||
Op OpCode
|
||||
IsParenthesized bool
|
||||
}
|
||||
|
||||
type EBoolean struct{ Value bool }
|
||||
|
||||
+122
-83
@@ -42,7 +42,7 @@ type parser struct {
|
||||
fnOnlyDataVisit fnOnlyDataVisit
|
||||
allocatedNames []string
|
||||
currentScope *js_ast.Scope
|
||||
scopesForCurrentPart []*js_ast.Scope
|
||||
currentPart *js_ast.Part
|
||||
symbols []ast.Symbol
|
||||
astHelpers js_ast.HelperContext
|
||||
tsUseCounts []uint32
|
||||
@@ -53,10 +53,6 @@ type parser struct {
|
||||
exprComments map[logger.Loc][]string
|
||||
mangledProps map[string]ast.Ref
|
||||
reservedProps map[string]bool
|
||||
symbolUses map[ast.Ref]js_ast.SymbolUse
|
||||
importSymbolPropertyUses map[ast.Ref]map[string]js_ast.SymbolUse
|
||||
symbolCallUses map[ast.Ref]js_ast.SymbolCallUse
|
||||
declaredSymbols []js_ast.DeclaredSymbol
|
||||
globPatternImports []globPatternImport
|
||||
runtimeImports map[string]ast.LocRef
|
||||
duplicateCaseChecker duplicateCaseChecker
|
||||
@@ -115,9 +111,8 @@ type parser struct {
|
||||
enclosingNamespaceArgRef *ast.Ref
|
||||
|
||||
// Imports (both ES6 and CommonJS) are tracked at the top level
|
||||
importRecords []ast.ImportRecord
|
||||
importRecordsForCurrentPart []uint32
|
||||
exportStarImportRecords []uint32
|
||||
importRecords []ast.ImportRecord
|
||||
exportStarImportRecords []uint32
|
||||
|
||||
// These are for handling ES6 imports and exports
|
||||
importItemsForNamespace map[ast.Ref]namespaceImportItems
|
||||
@@ -1735,9 +1730,11 @@ func (p *parser) recordUsage(ref ast.Ref) {
|
||||
// code regions since those will be culled.
|
||||
if !p.isControlFlowDead {
|
||||
p.symbols[ref.InnerIndex].UseCountEstimate++
|
||||
use := p.symbolUses[ref]
|
||||
use.CountEstimate++
|
||||
p.symbolUses[ref] = use
|
||||
if part := p.currentPart; part != nil {
|
||||
use := part.SymbolUses[ref]
|
||||
use.CountEstimate++
|
||||
part.SymbolUses[ref] = use
|
||||
}
|
||||
}
|
||||
|
||||
// The correctness of TypeScript-to-JavaScript conversion relies on accurate
|
||||
@@ -1752,12 +1749,16 @@ func (p *parser) ignoreUsage(ref ast.Ref) {
|
||||
// Roll back the use count increment in recordUsage()
|
||||
if !p.isControlFlowDead {
|
||||
p.symbols[ref.InnerIndex].UseCountEstimate--
|
||||
use := p.symbolUses[ref]
|
||||
use.CountEstimate--
|
||||
if use.CountEstimate == 0 {
|
||||
delete(p.symbolUses, ref)
|
||||
} else {
|
||||
p.symbolUses[ref] = use
|
||||
|
||||
// Only remove this usage if we're currently processing a part
|
||||
if part := p.currentPart; part != nil {
|
||||
use := part.SymbolUses[ref]
|
||||
use.CountEstimate--
|
||||
if use.CountEstimate == 0 {
|
||||
delete(part.SymbolUses, ref)
|
||||
} else {
|
||||
part.SymbolUses[ref] = use
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4262,6 +4263,7 @@ func (p *parser) parseExprCommon(level js_ast.L, errors *deferredErrors, flags e
|
||||
|
||||
func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredErrors, flags exprFlag) js_ast.Expr {
|
||||
optionalChain := js_ast.OptionalChainNone
|
||||
afterAsLoc := logger.Loc{Start: -1}
|
||||
|
||||
for {
|
||||
if p.lexer.Loc() == p.afterArrowBodyLoc {
|
||||
@@ -4281,9 +4283,11 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE
|
||||
}
|
||||
|
||||
// Stop now if this token is forbidden to follow a TypeScript "as" cast
|
||||
if p.lexer.Loc() == p.forbidSuffixAfterAsLoc {
|
||||
operatorRange := p.lexer.Range()
|
||||
if operatorRange.Loc == p.forbidSuffixAfterAsLoc {
|
||||
return left
|
||||
}
|
||||
isAfterAs := operatorRange.Loc == afterAsLoc
|
||||
|
||||
// Reset the optional chain flag by default. That way we won't accidentally
|
||||
// treat "c.d" as OptionalChainContinue in "a?.b + c.d".
|
||||
@@ -4915,7 +4919,7 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE
|
||||
if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot {
|
||||
r := logger.Range{Loc: left.Loc, Len: p.source.LocBeforeWhitespace(p.lexer.Loc()).Start - left.Loc.Start}
|
||||
data := p.tracker.MsgData(r, "Suspicious use of the \"!\" operator inside the \"in\" operator")
|
||||
data.Location.Suggestion = fmt.Sprintf("(%s)", p.source.TextForRange(r))
|
||||
data.Location.Suggestion = textForParenthesesSuggestion(p.source.TextForRange(r))
|
||||
p.log.AddMsgID(logger.MsgID_JS_SuspiciousBooleanNot, logger.Msg{
|
||||
Kind: kind,
|
||||
Data: data,
|
||||
@@ -4941,7 +4945,7 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE
|
||||
if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot {
|
||||
r := logger.Range{Loc: left.Loc, Len: p.source.LocBeforeWhitespace(p.lexer.Loc()).Start - left.Loc.Start}
|
||||
data := p.tracker.MsgData(r, "Suspicious use of the \"!\" operator inside the \"instanceof\" operator")
|
||||
data.Location.Suggestion = fmt.Sprintf("(%s)", p.source.TextForRange(r))
|
||||
data.Location.Suggestion = textForParenthesesSuggestion(p.source.TextForRange(r))
|
||||
p.log.AddMsgID(logger.MsgID_JS_SuspiciousBooleanNot, logger.Msg{
|
||||
Kind: kind,
|
||||
Data: data,
|
||||
@@ -4976,11 +4980,39 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE
|
||||
p.forbidSuffixAfterAsLoc = p.lexer.Loc()
|
||||
return left
|
||||
}
|
||||
afterAsLoc = p.lexer.Loc()
|
||||
continue
|
||||
}
|
||||
|
||||
return left
|
||||
}
|
||||
|
||||
// See: https://github.com/microsoft/TypeScript/issues/63527
|
||||
if isAfterAs {
|
||||
if binary, ok := left.Data.(*js_ast.EBinary); ok {
|
||||
if binaryLeft, ok := binary.Left.Data.(*js_ast.EBinary); ok && !binaryLeft.IsParenthesized {
|
||||
rightLevel := js_ast.OpTable[binary.Op].Level
|
||||
if binary.Op.IsRightAssociative() {
|
||||
rightLevel++
|
||||
}
|
||||
if rightLevel > js_ast.OpTable[binaryLeft.Op].Level {
|
||||
start := binary.Left.Loc
|
||||
end := p.source.LocBeforeWhitespace(operatorRange.Loc)
|
||||
wrapRange := logger.Range{Loc: start, Len: end.Start - start.Start}
|
||||
note := logger.MsgData{Text: "This is a syntax error in newer versions of TypeScript because the type cast has unintuitive precedence in this case."}
|
||||
if text := p.source.TextForRange(wrapRange); !strings.ContainsRune(text, '\n') {
|
||||
note = p.tracker.MsgData(wrapRange, note.Text+" Surround the inner expression in parentheses to silence this warning:")
|
||||
note.Location.Suggestion = textForParenthesesSuggestion(text)
|
||||
}
|
||||
p.log.AddIDWithNotes(logger.MsgID_JS_ConfusingTypeScriptCast, logger.Warning, &p.tracker, operatorRange,
|
||||
fmt.Sprintf("Operator %q should not directly follow a TypeScript type cast after the %q operator",
|
||||
js_ast.OpTable[binary.Op].Text, js_ast.OpTable[binaryLeft.Op].Text),
|
||||
[]logger.MsgData{note},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7019,7 +7051,7 @@ loop:
|
||||
var notes []logger.MsgData
|
||||
if text := p.source.TextForRange(wrapRange); !strings.ContainsRune(text, '\n') {
|
||||
note := p.tracker.MsgData(wrapRange, "Wrap this decorator in parentheses to allow arbitrary expressions:")
|
||||
note.Location.Suggestion = fmt.Sprintf("(%s)", text)
|
||||
note.Location.Suggestion = textForParenthesesSuggestion(text)
|
||||
notes = []logger.MsgData{note}
|
||||
}
|
||||
p.log.AddMsg(logger.Msg{
|
||||
@@ -7032,6 +7064,14 @@ loop:
|
||||
return memberExpr
|
||||
}
|
||||
|
||||
func textForParenthesesSuggestion(text string) string {
|
||||
count := utf8.RuneCountInString(text) - 2
|
||||
if count < 1 {
|
||||
count = 1
|
||||
}
|
||||
return fmt.Sprintf("(%s)", strings.Repeat(" ", count))
|
||||
}
|
||||
|
||||
type lexicalDecl uint8
|
||||
|
||||
const (
|
||||
@@ -8627,7 +8667,10 @@ func (p *parser) pushScopeForVisitPass(kind js_ast.ScopeKind, loc logger.Loc) {
|
||||
|
||||
p.scopesInOrder = p.scopesInOrder[1:]
|
||||
p.currentScope = order.scope
|
||||
p.scopesForCurrentPart = append(p.scopesForCurrentPart, order.scope)
|
||||
|
||||
if part := p.currentPart; part != nil {
|
||||
part.Scopes = append(part.Scopes, order.scope)
|
||||
}
|
||||
}
|
||||
|
||||
type findSymbolResult struct {
|
||||
@@ -10027,7 +10070,7 @@ func (p *parser) recordDeclaredSymbol(ref ast.Ref) {
|
||||
}
|
||||
}
|
||||
|
||||
p.declaredSymbols = append(p.declaredSymbols, js_ast.DeclaredSymbol{
|
||||
p.currentPart.DeclaredSymbols = append(p.currentPart.DeclaredSymbols, js_ast.DeclaredSymbol{
|
||||
Ref: ref,
|
||||
IsTopLevel: isTopLevel,
|
||||
})
|
||||
@@ -11867,6 +11910,8 @@ func (p *parser) markExprAsParenthesized(value js_ast.Expr, openParenLoc logger.
|
||||
e.IsParenthesized = true
|
||||
case *js_ast.EArrow:
|
||||
e.IsParenthesized = true
|
||||
case *js_ast.EBinary:
|
||||
e.IsParenthesized = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13084,22 +13129,25 @@ func (p *parser) maybeRewritePropertyAccess(
|
||||
if p.options.mode == config.ModeBundle && !p.isControlFlowDead {
|
||||
if id, ok := target.Data.(*js_ast.EImportIdentifier); ok {
|
||||
// Remove the normal symbol use
|
||||
use := p.symbolUses[id.Ref]
|
||||
symbolUses := p.currentPart.SymbolUses
|
||||
use := symbolUses[id.Ref]
|
||||
use.CountEstimate--
|
||||
if use.CountEstimate == 0 {
|
||||
delete(p.symbolUses, id.Ref)
|
||||
delete(symbolUses, id.Ref)
|
||||
} else {
|
||||
p.symbolUses[id.Ref] = use
|
||||
symbolUses[id.Ref] = use
|
||||
}
|
||||
|
||||
// Add a special symbol use instead
|
||||
if p.importSymbolPropertyUses == nil {
|
||||
p.importSymbolPropertyUses = make(map[ast.Ref]map[string]js_ast.SymbolUse)
|
||||
importSymbolPropertyUses := p.currentPart.ImportSymbolPropertyUses
|
||||
if importSymbolPropertyUses == nil {
|
||||
importSymbolPropertyUses = make(map[ast.Ref]map[string]js_ast.SymbolUse)
|
||||
p.currentPart.ImportSymbolPropertyUses = importSymbolPropertyUses
|
||||
}
|
||||
properties := p.importSymbolPropertyUses[id.Ref]
|
||||
properties := importSymbolPropertyUses[id.Ref]
|
||||
if properties == nil {
|
||||
properties = make(map[string]js_ast.SymbolUse)
|
||||
p.importSymbolPropertyUses[id.Ref] = properties
|
||||
importSymbolPropertyUses[id.Ref] = properties
|
||||
}
|
||||
use = properties[name]
|
||||
use.CountEstimate++
|
||||
@@ -15122,7 +15170,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
|
||||
record.Flags |= ast.HandlesImportErrors
|
||||
record.ErrorHandlerLoc = p.thenCatchChain.catchLoc
|
||||
}
|
||||
p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, importRecordIndex)
|
||||
p.currentPart.ImportRecordIndices = append(p.currentPart.ImportRecordIndices, importRecordIndex)
|
||||
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EImportString{
|
||||
ImportRecordIndex: importRecordIndex,
|
||||
CloseParenLoc: e.CloseParenLoc,
|
||||
@@ -15492,7 +15540,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
|
||||
record.Flags |= ast.HandlesImportErrors
|
||||
record.ErrorHandlerLoc = p.fnOrArrowDataVisit.tryCatchLoc
|
||||
}
|
||||
p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, importRecordIndex)
|
||||
p.currentPart.ImportRecordIndices = append(p.currentPart.ImportRecordIndices, importRecordIndex)
|
||||
|
||||
// Create a new expression to represent the operation
|
||||
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ERequireResolveString{
|
||||
@@ -15710,7 +15758,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
|
||||
record.Flags |= ast.HandlesImportErrors
|
||||
record.ErrorHandlerLoc = p.fnOrArrowDataVisit.tryCatchLoc
|
||||
}
|
||||
p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, importRecordIndex)
|
||||
p.currentPart.ImportRecordIndices = append(p.currentPart.ImportRecordIndices, importRecordIndex)
|
||||
|
||||
// Currently "require" is not converted into "import" for ESM
|
||||
if p.options.mode != config.ModeBundle && p.options.outputFormat == config.FormatESModule && !omitWarnings {
|
||||
@@ -16785,24 +16833,27 @@ func (p *parser) globPatternFromExpr(expr js_ast.Expr) ([]globPart, logger.Range
|
||||
|
||||
func (p *parser) convertSymbolUseToCall(ref ast.Ref, isSingleNonSpreadArgCall bool) {
|
||||
// Remove the normal symbol use
|
||||
use := p.symbolUses[ref]
|
||||
symbolUses := p.currentPart.SymbolUses
|
||||
use := symbolUses[ref]
|
||||
use.CountEstimate--
|
||||
if use.CountEstimate == 0 {
|
||||
delete(p.symbolUses, ref)
|
||||
delete(symbolUses, ref)
|
||||
} else {
|
||||
p.symbolUses[ref] = use
|
||||
symbolUses[ref] = use
|
||||
}
|
||||
|
||||
// Add a special symbol use instead
|
||||
if p.symbolCallUses == nil {
|
||||
p.symbolCallUses = make(map[ast.Ref]js_ast.SymbolCallUse)
|
||||
symbolCallUses := p.currentPart.SymbolCallUses
|
||||
if symbolCallUses == nil {
|
||||
symbolCallUses = make(map[ast.Ref]js_ast.SymbolCallUse)
|
||||
p.currentPart.SymbolCallUses = symbolCallUses
|
||||
}
|
||||
callUse := p.symbolCallUses[ref]
|
||||
callUse := symbolCallUses[ref]
|
||||
callUse.CallCountEstimate++
|
||||
if isSingleNonSpreadArgCall {
|
||||
callUse.SingleArgNonSpreadCallCountEstimate++
|
||||
}
|
||||
p.symbolCallUses[ref] = callUse
|
||||
symbolCallUses[ref] = callUse
|
||||
}
|
||||
|
||||
func (p *parser) warnAboutImportNamespaceCall(target js_ast.Expr, kind importNamespaceCallKind) {
|
||||
@@ -17468,7 +17519,7 @@ func (p *parser) scanForImportsAndExports(stmts []js_ast.Stmt) (result importsEx
|
||||
}
|
||||
|
||||
// Also record these automatically-generated top-level namespace alias symbols
|
||||
p.declaredSymbols = append(p.declaredSymbols, js_ast.DeclaredSymbol{
|
||||
p.currentPart.DeclaredSymbols = append(p.currentPart.DeclaredSymbols, js_ast.DeclaredSymbol{
|
||||
Ref: name.Ref,
|
||||
IsTopLevel: true,
|
||||
})
|
||||
@@ -17506,7 +17557,7 @@ func (p *parser) scanForImportsAndExports(stmts []js_ast.Stmt) (result importsEx
|
||||
}
|
||||
}
|
||||
|
||||
p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, s.ImportRecordIndex)
|
||||
p.currentPart.ImportRecordIndices = append(p.currentPart.ImportRecordIndices, s.ImportRecordIndex)
|
||||
|
||||
if s.StarNameLoc != nil {
|
||||
record.Flags |= ast.ContainsImportStar
|
||||
@@ -17557,7 +17608,7 @@ func (p *parser) scanForImportsAndExports(stmts []js_ast.Stmt) (result importsEx
|
||||
|
||||
case *js_ast.SExportStar:
|
||||
record := &p.importRecords[s.ImportRecordIndex]
|
||||
p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, s.ImportRecordIndex)
|
||||
p.currentPart.ImportRecordIndices = append(p.currentPart.ImportRecordIndices, s.ImportRecordIndex)
|
||||
|
||||
if s.Alias != nil {
|
||||
// "export * as ns from 'path'"
|
||||
@@ -17578,7 +17629,7 @@ func (p *parser) scanForImportsAndExports(stmts []js_ast.Stmt) (result importsEx
|
||||
|
||||
case *js_ast.SExportFrom:
|
||||
record := &p.importRecords[s.ImportRecordIndex]
|
||||
p.importRecordsForCurrentPart = append(p.importRecordsForCurrentPart, s.ImportRecordIndex)
|
||||
p.currentPart.ImportRecordIndices = append(p.currentPart.ImportRecordIndices, s.ImportRecordIndex)
|
||||
|
||||
for _, item := range s.Items {
|
||||
// Note that the imported alias is not item.Alias, which is the
|
||||
@@ -17629,17 +17680,11 @@ func (p *parser) scanForImportsAndExports(stmts []js_ast.Stmt) (result importsEx
|
||||
}
|
||||
|
||||
func (p *parser) appendPart(parts []js_ast.Part, stmts []js_ast.Stmt) []js_ast.Part {
|
||||
p.symbolUses = make(map[ast.Ref]js_ast.SymbolUse)
|
||||
p.importSymbolPropertyUses = nil
|
||||
p.symbolCallUses = nil
|
||||
p.declaredSymbols = nil
|
||||
p.importRecordsForCurrentPart = nil
|
||||
p.scopesForCurrentPart = nil
|
||||
|
||||
part := js_ast.Part{
|
||||
Stmts: p.visitStmtsAndPrependTempRefs(stmts, prependTempRefsOpts{}),
|
||||
SymbolUses: p.symbolUses,
|
||||
SymbolUses: make(map[ast.Ref]js_ast.SymbolUse),
|
||||
}
|
||||
p.currentPart = &part
|
||||
part.Stmts = p.visitStmtsAndPrependTempRefs(stmts, prependTempRefsOpts{})
|
||||
|
||||
// Sanity check
|
||||
if p.currentScope != p.moduleScope {
|
||||
@@ -17681,13 +17726,11 @@ func (p *parser) appendPart(parts []js_ast.Part, stmts []js_ast.Stmt) []js_ast.P
|
||||
flags |= js_ast.KeepExportClauses
|
||||
}
|
||||
part.CanBeRemovedIfUnused = p.astHelpers.StmtsCanBeRemovedIfUnused(part.Stmts, flags)
|
||||
part.DeclaredSymbols = p.declaredSymbols
|
||||
part.ImportRecordIndices = p.importRecordsForCurrentPart
|
||||
part.ImportSymbolPropertyUses = p.importSymbolPropertyUses
|
||||
part.SymbolCallUses = p.symbolCallUses
|
||||
part.Scopes = p.scopesForCurrentPart
|
||||
parts = append(parts, part)
|
||||
}
|
||||
|
||||
// Reset the state for this part so we don't accidentally mutate it
|
||||
p.currentPart = nil
|
||||
return parts
|
||||
}
|
||||
|
||||
@@ -18098,9 +18141,14 @@ func LazyExportAST(log logger.Log, source logger.Source, options Options, expr j
|
||||
p := newParser(log, source, js_lexer.Lexer{}, &options)
|
||||
p.prepareForVisitPass()
|
||||
|
||||
// Defer the actual code generation until linking
|
||||
part := js_ast.Part{
|
||||
SymbolUses: make(map[ast.Ref]js_ast.SymbolUse),
|
||||
}
|
||||
|
||||
// Optionally call a runtime API function to transform the expression
|
||||
if helperCall != nil {
|
||||
p.symbolUses = make(map[ast.Ref]js_ast.SymbolUse)
|
||||
p.currentPart = &part
|
||||
if len(helperCall.Global) > 0 {
|
||||
ref := p.newSymbol(ast.SymbolUnbound, helperCall.Global[0])
|
||||
p.recordUsage(ref)
|
||||
@@ -18114,7 +18162,9 @@ func LazyExportAST(log logger.Log, source logger.Source, options Options, expr j
|
||||
} else {
|
||||
expr = p.callRuntime(expr.Loc, helperCall.Runtime, []js_ast.Expr{expr})
|
||||
}
|
||||
p.currentPart = nil
|
||||
}
|
||||
part.Stmts = []js_ast.Stmt{{Loc: expr.Loc, Data: &js_ast.SLazyExport{Value: expr}}}
|
||||
|
||||
// Add an empty part for the namespace export that we can fill in later
|
||||
nsExportPart := js_ast.Part{
|
||||
@@ -18122,13 +18172,6 @@ func LazyExportAST(log logger.Log, source logger.Source, options Options, expr j
|
||||
CanBeRemovedIfUnused: true,
|
||||
}
|
||||
|
||||
// Defer the actual code generation until linking
|
||||
part := js_ast.Part{
|
||||
Stmts: []js_ast.Stmt{{Loc: expr.Loc, Data: &js_ast.SLazyExport{Value: expr}}},
|
||||
SymbolUses: p.symbolUses,
|
||||
}
|
||||
p.symbolUses = nil
|
||||
|
||||
ast := p.toAST([]js_ast.Part{nsExportPart}, []js_ast.Part{part}, nil, "", nil)
|
||||
ast.HasLazyExport = true
|
||||
return ast
|
||||
@@ -18157,22 +18200,22 @@ func GlobResolveAST(log logger.Log, source logger.Source, importRecords []ast.Im
|
||||
importRecordIndices = append(importRecordIndices, uint32(importRecordIndex))
|
||||
}
|
||||
|
||||
p.symbolUses = make(map[ast.Ref]js_ast.SymbolUse)
|
||||
ref := p.newSymbol(ast.SymbolOther, name)
|
||||
p.moduleScope.Generated = append(p.moduleScope.Generated, ref)
|
||||
|
||||
part := js_ast.Part{
|
||||
Stmts: []js_ast.Stmt{{Data: &js_ast.SLocal{
|
||||
IsExport: true,
|
||||
Decls: []js_ast.Decl{{
|
||||
Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: ref}},
|
||||
ValueOrNil: p.callRuntime(logger.Loc{}, "__glob", []js_ast.Expr{{Data: object}}),
|
||||
}},
|
||||
}}},
|
||||
ImportRecordIndices: importRecordIndices,
|
||||
SymbolUses: p.symbolUses,
|
||||
SymbolUses: make(map[ast.Ref]js_ast.SymbolUse),
|
||||
}
|
||||
p.symbolUses = nil
|
||||
|
||||
p.currentPart = &part
|
||||
part.Stmts = []js_ast.Stmt{{Data: &js_ast.SLocal{
|
||||
IsExport: true,
|
||||
Decls: []js_ast.Decl{{
|
||||
Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: ref}},
|
||||
ValueOrNil: p.callRuntime(logger.Loc{}, "__glob", []js_ast.Expr{{Data: object}}),
|
||||
}},
|
||||
}}}
|
||||
p.currentPart = nil
|
||||
|
||||
p.esmExportKeyword.Len = 1
|
||||
return p.toAST([]js_ast.Part{nsExportPart}, []js_ast.Part{part}, nil, "", nil)
|
||||
@@ -18628,17 +18671,13 @@ func (p *parser) toAST(before, parts, after []js_ast.Part, hashbang string, dire
|
||||
removedImportEquals := false
|
||||
partsEnd := 0
|
||||
for partIndex, part := range parts {
|
||||
p.importRecordsForCurrentPart = nil
|
||||
p.declaredSymbols = nil
|
||||
|
||||
p.currentPart = &part
|
||||
result := p.scanForImportsAndExports(part.Stmts)
|
||||
p.currentPart = nil
|
||||
part.Stmts = result.stmts
|
||||
keptImportEquals = keptImportEquals || result.keptImportEquals
|
||||
removedImportEquals = removedImportEquals || result.removedImportEquals
|
||||
|
||||
part.ImportRecordIndices = append(part.ImportRecordIndices, p.importRecordsForCurrentPart...)
|
||||
part.DeclaredSymbols = append(part.DeclaredSymbols, p.declaredSymbols...)
|
||||
|
||||
if len(part.Stmts) > 0 || uint32(partIndex) == js_ast.NSExportPartIndex {
|
||||
if p.moduleScope.ContainsDirectEval && len(part.DeclaredSymbols) > 0 {
|
||||
// If this file contains a direct call to "eval()", all parts that
|
||||
|
||||
@@ -382,7 +382,7 @@ func (p *parser) lowerFunction(
|
||||
|
||||
// Forward the arguments to the wrapper function
|
||||
usesArgumentsRef := !isArrow && p.fnOnlyDataVisit.argumentsRef != nil &&
|
||||
p.symbolUses[*p.fnOnlyDataVisit.argumentsRef].CountEstimate > 0
|
||||
p.currentPart.SymbolUses[*p.fnOnlyDataVisit.argumentsRef].CountEstimate > 0
|
||||
var forwardedArgs js_ast.Expr
|
||||
if !couldThrowErrors && !usesArgumentsRef {
|
||||
// Simple case: the arguments can stay on the outer function. It's
|
||||
@@ -849,6 +849,26 @@ func (p *parser) lowerAssignmentOperator(value js_ast.Expr, callback func(js_ast
|
||||
}
|
||||
|
||||
case *js_ast.EIdentifier:
|
||||
// Make sure we record this usage in the usage count so that duplicating
|
||||
// a single-use reference means it's no longer considered a single-use
|
||||
// reference. Otherwise the single-use reference inlining code may
|
||||
// incorrectly inline the initializer into the first reference, leaving
|
||||
// the second reference without a definition. For example:
|
||||
//
|
||||
// let x;
|
||||
// x ||= {};
|
||||
//
|
||||
// We're changing that to this:
|
||||
//
|
||||
// let x;
|
||||
// x || (x = {});
|
||||
//
|
||||
// And we don't want to believe that "x" still has a single reference, as
|
||||
// then we may incorrectly inline the undefined value like this:
|
||||
//
|
||||
// void 0 || (x = {});
|
||||
//
|
||||
p.recordUsage(left.Ref)
|
||||
return callback(
|
||||
js_ast.Expr{Loc: value.Loc, Data: &js_ast.EIdentifier{Ref: left.Ref}},
|
||||
value,
|
||||
@@ -2011,7 +2031,7 @@ func (ctx *lowerUsingDeclarationContext) finalize(p *parser, stmts []js_ast.Stmt
|
||||
}
|
||||
isTopLevel := scope == p.moduleScope
|
||||
scope.Generated = append(scope.Generated, ctx.stackRef, caughtRef, errorRef, hasErrorRef)
|
||||
p.declaredSymbols = append(p.declaredSymbols,
|
||||
p.currentPart.DeclaredSymbols = append(p.currentPart.DeclaredSymbols,
|
||||
js_ast.DeclaredSymbol{IsTopLevel: isTopLevel, Ref: ctx.stackRef},
|
||||
js_ast.DeclaredSymbol{IsTopLevel: isTopLevel, Ref: caughtRef},
|
||||
js_ast.DeclaredSymbol{IsTopLevel: isTopLevel, Ref: errorRef},
|
||||
@@ -2034,7 +2054,7 @@ func (ctx *lowerUsingDeclarationContext) finalize(p *parser, stmts []js_ast.Stmt
|
||||
if ctx.hasAwaitUsing {
|
||||
promiseRef := p.generateTempRef(tempRefNoDeclare, "_promise")
|
||||
scope.Generated = append(scope.Generated, promiseRef)
|
||||
p.declaredSymbols = append(p.declaredSymbols, js_ast.DeclaredSymbol{IsTopLevel: isTopLevel, Ref: promiseRef})
|
||||
p.currentPart.DeclaredSymbols = append(p.currentPart.DeclaredSymbols, js_ast.DeclaredSymbol{IsTopLevel: isTopLevel, Ref: promiseRef})
|
||||
|
||||
// "await" expressions turn into "yield" expressions when lowering
|
||||
p.recordUsage(promiseRef)
|
||||
|
||||
@@ -160,6 +160,9 @@ _x = new WeakMap();
|
||||
// Temporary variables should not come before "use strict"
|
||||
expectPrintedTarget(t, 2019, "function f() { /*! @license */ 'use strict'; a.b ??= c.d }",
|
||||
"function f() {\n /*! @license */\n \"use strict\";\n var _a;\n (_a = a.b) != null ? _a : a.b = c.d;\n}\n")
|
||||
|
||||
// https://github.com/evanw/esbuild/issues/4508
|
||||
expectPrintedMangleTarget(t, 2020, "function foo() { let x; x ??= {} }", "function foo() {\n let x;\n x ?? (x = {});\n}\n")
|
||||
}
|
||||
|
||||
func TestLowerLogicalAssign(t *testing.T) {
|
||||
@@ -229,6 +232,10 @@ class Foo {
|
||||
}
|
||||
_x = new WeakMap();
|
||||
`)
|
||||
|
||||
// https://github.com/evanw/esbuild/issues/4508
|
||||
expectPrintedMangleTarget(t, 2020, "function foo() { let x; x ||= {} }", "function foo() {\n let x;\n x || (x = {});\n}\n")
|
||||
expectPrintedMangleTarget(t, 2020, "function foo() { let x; x &&= {} }", "function foo() {\n let x;\n x && (x = {});\n}\n")
|
||||
}
|
||||
|
||||
func TestLowerAsyncFunctions(t *testing.T) {
|
||||
|
||||
@@ -3324,3 +3324,34 @@ func TestTSUsing(t *testing.T) {
|
||||
expectParseErrorTS(t, "export using x: any = y", "<stdin>: ERROR: Unexpected \"using\"\n")
|
||||
expectParseErrorTS(t, "namespace ns { export using x: any = y }", "<stdin>: ERROR: Unexpected \"using\"\n")
|
||||
}
|
||||
|
||||
func TestTSWarningBinaryOperatorAfterCast(t *testing.T) {
|
||||
note := "<stdin>: NOTE: This is a syntax error in newer versions of TypeScript because the type cast has unintuitive precedence in this case. " +
|
||||
"Surround the inner expression in parentheses to silence this warning:\n"
|
||||
|
||||
// See: https://github.com/microsoft/TypeScript/issues/63527
|
||||
expectParseErrorTS(t, "1 as number + 2", "")
|
||||
expectParseErrorTS(t, "1 + 2 as number + 3", "")
|
||||
expectParseErrorTS(t, "1 + 2 as number - 3", "")
|
||||
expectParseErrorTS(t, "1 * 2 as number * 3", "")
|
||||
expectParseErrorTS(t, "1 * 2 as number + 3", "")
|
||||
expectParseErrorTS(t, "1 + 2 as number * 3", "<stdin>: WARNING: Operator \"*\" should not directly follow a TypeScript type cast after the \"+\" operator\n"+note)
|
||||
expectParseErrorTS(t, "(1 + 2) as number * 3", "")
|
||||
expectParseErrorTS(t, "(1 + 2 as number) * 3", "")
|
||||
expectParseErrorTS(t, "1 satisfies number + 2", "")
|
||||
expectParseErrorTS(t, "1 + 2 satisfies number + 3", "")
|
||||
expectParseErrorTS(t, "1 + 2 satisfies number - 3", "")
|
||||
expectParseErrorTS(t, "1 * 2 satisfies number * 3", "")
|
||||
expectParseErrorTS(t, "1 * 2 satisfies number + 3", "")
|
||||
expectParseErrorTS(t, "1 + 2 satisfies number * 3", "<stdin>: WARNING: Operator \"*\" should not directly follow a TypeScript type cast after the \"+\" operator\n"+note)
|
||||
expectParseErrorTS(t, "(1 + 2) satisfies number * 3", "")
|
||||
expectParseErrorTS(t, "(1 + 2 satisfies number) * 3", "")
|
||||
|
||||
// See: https://github.com/microsoft/TypeScript/issues/63661
|
||||
expectParseErrorTS(t, "1 ** 2 as number ** 3", "<stdin>: WARNING: Operator \"**\" should not directly follow a TypeScript type cast after the \"**\" operator\n"+note)
|
||||
expectParseErrorTS(t, "(1 ** 2) as number ** 3", "")
|
||||
expectParseErrorTS(t, "(1 ** 2 as number) ** 3", "")
|
||||
expectParseErrorTS(t, "1 ** 2 satisfies number ** 3", "<stdin>: WARNING: Operator \"**\" should not directly follow a TypeScript type cast after the \"**\" operator\n"+note)
|
||||
expectParseErrorTS(t, "(1 ** 2) satisfies number ** 3", "")
|
||||
expectParseErrorTS(t, "(1 ** 2 satisfies number) ** 3", "")
|
||||
}
|
||||
|
||||
@@ -663,6 +663,7 @@ func (c *linkerContext) generateChunksInParallel(additionalFiles []graph.OutputF
|
||||
// output directory. This is used by the "file" and "copy" loaders.
|
||||
var commentPrefix string
|
||||
var commentSuffix string
|
||||
var canBeMerged bool
|
||||
switch chunkRepr := chunk.chunkRepr.(type) {
|
||||
case *chunkReprJS:
|
||||
for _, sourceIndex := range chunkRepr.filesInChunkInOrder {
|
||||
@@ -678,6 +679,7 @@ func (c *linkerContext) generateChunksInParallel(additionalFiles []graph.OutputF
|
||||
}
|
||||
commentPrefix = "/*"
|
||||
commentSuffix = " */"
|
||||
canBeMerged = true
|
||||
}
|
||||
|
||||
// Path substitution for the chunk itself
|
||||
@@ -708,6 +710,7 @@ func (c *linkerContext) generateChunksInParallel(additionalFiles []graph.OutputF
|
||||
JSONMetadataChunk: fmt.Sprintf(
|
||||
c.options.MetafileFormat.MaybeRemoveWhitespace("{\n \"imports\": [],\n \"exports\": [],\n \"inputs\": {},\n \"bytes\": %d\n }"),
|
||||
len(chunk.externalLegalComments)),
|
||||
CanBeMerged: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -747,6 +750,7 @@ func (c *linkerContext) generateChunksInParallel(additionalFiles []graph.OutputF
|
||||
JSONMetadataChunk: fmt.Sprintf(
|
||||
c.options.MetafileFormat.MaybeRemoveWhitespace("{\n \"imports\": [],\n \"exports\": [],\n \"inputs\": {},\n \"bytes\": %d\n }"),
|
||||
len(outputSourceMap)),
|
||||
CanBeMerged: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -771,6 +775,7 @@ func (c *linkerContext) generateChunksInParallel(additionalFiles []graph.OutputF
|
||||
Contents: outputContents,
|
||||
JSONMetadataChunk: jsonMetadataChunk,
|
||||
IsExecutable: chunk.isExecutable,
|
||||
CanBeMerged: canBeMerged,
|
||||
})
|
||||
|
||||
results[chunkIndex] = outputFiles
|
||||
|
||||
+16
-443
@@ -839,6 +839,7 @@ func OutputOptionsForArgs(osArgs []string) OutputOptions {
|
||||
options.Color = ColorNever
|
||||
case "--color=true", "--color":
|
||||
options.Color = ColorAlways
|
||||
|
||||
case "--log-level=info":
|
||||
options.LogLevel = LevelInfo
|
||||
case "--log-level=warning":
|
||||
@@ -847,6 +848,13 @@ func OutputOptionsForArgs(osArgs []string) OutputOptions {
|
||||
options.LogLevel = LevelError
|
||||
case "--log-level=silent":
|
||||
options.LogLevel = LevelSilent
|
||||
|
||||
case "--log-style=default":
|
||||
options.LogStyle = StyleDefault
|
||||
case "--log-level=clang":
|
||||
options.LogStyle = StyleClang
|
||||
case "--log-level=visualstudio":
|
||||
options.LogStyle = StyleVisualStudio
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,275 +1210,27 @@ type OutputOptions struct {
|
||||
IncludeSource bool
|
||||
Color UseColor
|
||||
LogLevel LogLevel
|
||||
LogStyle LogStyle
|
||||
PathStyle PathStyle
|
||||
Overrides map[MsgID]LogLevel
|
||||
}
|
||||
|
||||
func (msg Msg) String(options OutputOptions, terminalInfo TerminalInfo) string {
|
||||
// Format the message
|
||||
var text strings.Builder
|
||||
text.WriteString(msgString(options.IncludeSource, options.PathStyle, terminalInfo, msg.ID, msg.Kind, msg.Data, msg.PluginName))
|
||||
switch options.LogStyle {
|
||||
case StyleClang:
|
||||
return msgToStringClang(msg, options, terminalInfo)
|
||||
|
||||
// Format the notes
|
||||
var oldData MsgData
|
||||
for i, note := range msg.Notes {
|
||||
if options.IncludeSource && (i == 0 || strings.IndexByte(oldData.Text, '\n') >= 0 || oldData.Location != nil) {
|
||||
text.WriteString("\n")
|
||||
}
|
||||
text.WriteString(msgString(options.IncludeSource, options.PathStyle, terminalInfo, MsgID_None, Note, note, ""))
|
||||
oldData = note
|
||||
}
|
||||
case StyleVisualStudio:
|
||||
return msgToStringVisualStudio(msg, options, terminalInfo)
|
||||
|
||||
// Add extra spacing between messages if source code is present
|
||||
if options.IncludeSource {
|
||||
text.WriteString("\n")
|
||||
default:
|
||||
return msgToStringDefault(msg, options, terminalInfo)
|
||||
}
|
||||
return text.String()
|
||||
}
|
||||
|
||||
// The number of margin characters in addition to the line number
|
||||
const extraMarginChars = 9
|
||||
|
||||
func marginWithLineText(maxMargin int, line int) string {
|
||||
number := fmt.Sprintf("%d", line)
|
||||
return fmt.Sprintf(" %s%s │ ", strings.Repeat(" ", maxMargin-len(number)), number)
|
||||
}
|
||||
|
||||
func emptyMarginText(maxMargin int, isLast bool) string {
|
||||
space := strings.Repeat(" ", maxMargin)
|
||||
if isLast {
|
||||
return fmt.Sprintf(" %s ╵ ", space)
|
||||
}
|
||||
return fmt.Sprintf(" %s │ ", space)
|
||||
}
|
||||
|
||||
func msgString(includeSource bool, pathStyle PathStyle, terminalInfo TerminalInfo, id MsgID, kind MsgKind, data MsgData, pluginName string) string {
|
||||
if !includeSource {
|
||||
if loc := data.Location; loc != nil {
|
||||
return fmt.Sprintf("%s: %s: %s\n", loc.File.Select(pathStyle), kind.String(), data.Text)
|
||||
}
|
||||
return fmt.Sprintf("%s: %s\n", kind.String(), data.Text)
|
||||
}
|
||||
|
||||
var colors Colors
|
||||
if terminalInfo.UseColorEscapes {
|
||||
colors = TerminalColors
|
||||
}
|
||||
|
||||
var iconColor string
|
||||
var kindColorBrackets string
|
||||
var kindColorText string
|
||||
|
||||
location := ""
|
||||
|
||||
if data.Location != nil {
|
||||
maxMargin := len(fmt.Sprintf("%d", data.Location.Line))
|
||||
d := detailStruct(data, pathStyle, terminalInfo, maxMargin)
|
||||
|
||||
if d.Suggestion != "" {
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s\n%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
d.Path, d.Line, d.Column,
|
||||
colors.Dim, d.SourceBefore, colors.Green, d.SourceMarked, colors.Dim, d.SourceAfter,
|
||||
emptyMarginText(maxMargin, false), d.Indent, colors.Green, d.Marker, colors.Dim,
|
||||
emptyMarginText(maxMargin, true), d.Indent, colors.Green, d.Suggestion, colors.Reset,
|
||||
d.ContentAfter,
|
||||
)
|
||||
} else {
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
d.Path, d.Line, d.Column,
|
||||
colors.Dim, d.SourceBefore, colors.Green, d.SourceMarked, colors.Dim, d.SourceAfter,
|
||||
emptyMarginText(maxMargin, true), d.Indent, colors.Green, d.Marker, colors.Reset,
|
||||
d.ContentAfter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case Verbose:
|
||||
iconColor = colors.Cyan
|
||||
kindColorBrackets = colors.CyanBgCyan
|
||||
kindColorText = colors.CyanBgBlack
|
||||
|
||||
case Debug:
|
||||
iconColor = colors.Green
|
||||
kindColorBrackets = colors.GreenBgGreen
|
||||
kindColorText = colors.GreenBgWhite
|
||||
|
||||
case Info:
|
||||
iconColor = colors.Blue
|
||||
kindColorBrackets = colors.BlueBgBlue
|
||||
kindColorText = colors.BlueBgWhite
|
||||
|
||||
case Error:
|
||||
iconColor = colors.Red
|
||||
kindColorBrackets = colors.RedBgRed
|
||||
kindColorText = colors.RedBgWhite
|
||||
|
||||
case Warning:
|
||||
iconColor = colors.Yellow
|
||||
kindColorBrackets = colors.YellowBgYellow
|
||||
kindColorText = colors.YellowBgBlack
|
||||
|
||||
case Note:
|
||||
sb := strings.Builder{}
|
||||
|
||||
for _, line := range strings.Split(data.Text, "\n") {
|
||||
// Special-case word wrapping
|
||||
if wrapWidth := terminalInfo.Width; wrapWidth > 2 {
|
||||
if !data.DisableMaximumWidth && wrapWidth > 100 {
|
||||
wrapWidth = 100 // Enforce a maximum paragraph width for readability
|
||||
}
|
||||
for _, run := range wrapWordsInString(line, wrapWidth-2) {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(linkifyText(run, colors.Underline, colors.Reset))
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, just write an indented line
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(linkifyText(line, colors.Underline, colors.Reset))
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
sb.WriteString(location)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
if pluginName != "" {
|
||||
pluginName = fmt.Sprintf(" %s%s[plugin %s]%s", colors.Bold, colors.Magenta, pluginName, colors.Reset)
|
||||
}
|
||||
|
||||
msgID := MsgIDToString(id)
|
||||
if msgID != "" {
|
||||
msgID = fmt.Sprintf(" [%s]", msgID)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s %s[%s%s%s]%s %s%s%s%s%s\n%s",
|
||||
iconColor, kind.Icon(),
|
||||
kindColorBrackets, kindColorText, kind.String(), kindColorBrackets, colors.Reset,
|
||||
colors.Bold, data.Text, colors.Reset, pluginName, msgID,
|
||||
location,
|
||||
)
|
||||
}
|
||||
|
||||
func linkifyText(text string, underline string, reset string) string {
|
||||
if underline == "" {
|
||||
return text
|
||||
}
|
||||
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
return text
|
||||
}
|
||||
|
||||
sb := strings.Builder{}
|
||||
for {
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
end := strings.IndexByte(text[https:], ' ')
|
||||
if end == -1 {
|
||||
end = len(text)
|
||||
} else {
|
||||
end += https
|
||||
}
|
||||
|
||||
// Remove trailing punctuation
|
||||
if end > https {
|
||||
switch text[end-1] {
|
||||
case '.', ',', '?', '!', ')', ']', '}':
|
||||
end--
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(text[:https])
|
||||
sb.WriteString(underline)
|
||||
sb.WriteString(text[https:end])
|
||||
sb.WriteString(reset)
|
||||
text = text[end:]
|
||||
}
|
||||
|
||||
sb.WriteString(text)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func wrapWordsInString(text string, width int) []string {
|
||||
runs := []string{}
|
||||
|
||||
outer:
|
||||
for text != "" {
|
||||
i := 0
|
||||
x := 0
|
||||
wordEndI := 0
|
||||
|
||||
// Skip over any leading spaces
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
|
||||
// Find out how many words will fit in this run
|
||||
for i < len(text) {
|
||||
oldWordEndI := wordEndI
|
||||
wordStartI := i
|
||||
|
||||
// Find the end of the word
|
||||
for i < len(text) {
|
||||
c, width := utf8.DecodeRuneInString(text[i:])
|
||||
if c == ' ' {
|
||||
break
|
||||
}
|
||||
i += width
|
||||
x += 1 // Naively assume that each unicode code point is a single column
|
||||
}
|
||||
wordEndI = i
|
||||
|
||||
// Split into a new run if this isn't the first word in the run and the end is past the width
|
||||
if wordStartI > 0 && x > width {
|
||||
runs = append(runs, text[:oldWordEndI])
|
||||
text = text[wordStartI:]
|
||||
continue outer
|
||||
}
|
||||
|
||||
// Skip over any spaces after the word
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, this is the last run (i.e. everything fits)
|
||||
break
|
||||
}
|
||||
|
||||
// Remove any trailing spaces on the last run
|
||||
for len(text) > 0 && text[len(text)-1] == ' ' {
|
||||
text = text[:len(text)-1]
|
||||
}
|
||||
runs = append(runs, text)
|
||||
return runs
|
||||
}
|
||||
|
||||
type MsgDetail struct {
|
||||
SourceBefore string
|
||||
SourceMarked string
|
||||
SourceAfter string
|
||||
|
||||
Indent string
|
||||
Marker string
|
||||
Suggestion string
|
||||
|
||||
ContentAfter string
|
||||
|
||||
Path string
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// It's not common for large files to have many warnings. But when it happens,
|
||||
// we want to make sure that it's not too slow. Source code locations are
|
||||
// represented as byte offsets for compactness but transforming these to
|
||||
@@ -1639,193 +1399,6 @@ func (tracker *LineColumnTracker) MsgLocationOrNil(r Range) *MsgLocation {
|
||||
}
|
||||
}
|
||||
|
||||
func detailStruct(data MsgData, pathStyle PathStyle, terminalInfo TerminalInfo, maxMargin int) MsgDetail {
|
||||
// Only highlight the first line of the line text
|
||||
loc := *data.Location
|
||||
endOfFirstLine := len(loc.LineText)
|
||||
|
||||
// Note: This uses "IndexByte" because Go implements this with SIMD, which
|
||||
// can matter a lot for really long lines. Some people pass huge >100mb
|
||||
// minified files as line text for the log message.
|
||||
if i := strings.IndexByte(loc.LineText, '\n'); i >= 0 {
|
||||
endOfFirstLine = i
|
||||
}
|
||||
|
||||
firstLine := loc.LineText[:endOfFirstLine]
|
||||
afterFirstLine := loc.LineText[endOfFirstLine:]
|
||||
if afterFirstLine != "" && !strings.HasSuffix(afterFirstLine, "\n") {
|
||||
afterFirstLine += "\n"
|
||||
}
|
||||
|
||||
// Clamp values in range
|
||||
if loc.Line < 0 {
|
||||
loc.Line = 0
|
||||
}
|
||||
if loc.Column < 0 {
|
||||
loc.Column = 0
|
||||
}
|
||||
if loc.Length < 0 {
|
||||
loc.Length = 0
|
||||
}
|
||||
if loc.Column > endOfFirstLine {
|
||||
loc.Column = endOfFirstLine
|
||||
}
|
||||
if loc.Length > endOfFirstLine-loc.Column {
|
||||
loc.Length = endOfFirstLine - loc.Column
|
||||
}
|
||||
|
||||
spacesPerTab := 2
|
||||
lineText := renderTabStops(firstLine, spacesPerTab)
|
||||
textUpToLoc := renderTabStops(firstLine[:loc.Column], spacesPerTab)
|
||||
markerStart := len(textUpToLoc)
|
||||
markerEnd := markerStart
|
||||
indent := strings.Repeat(" ", estimateWidthInTerminal(textUpToLoc))
|
||||
marker := "^"
|
||||
|
||||
// Extend markers to cover the full range of the error
|
||||
if loc.Length > 0 {
|
||||
markerEnd = len(renderTabStops(firstLine[:loc.Column+loc.Length], spacesPerTab))
|
||||
}
|
||||
|
||||
// Clip the marker to the bounds of the line
|
||||
if markerStart > len(lineText) {
|
||||
markerStart = len(lineText)
|
||||
}
|
||||
if markerEnd > len(lineText) {
|
||||
markerEnd = len(lineText)
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
|
||||
// Trim the line to fit the terminal width
|
||||
width := terminalInfo.Width
|
||||
if width < 1 {
|
||||
width = defaultTerminalWidth
|
||||
}
|
||||
width -= maxMargin + extraMarginChars
|
||||
if width < 1 {
|
||||
width = 1
|
||||
}
|
||||
if loc.Column == endOfFirstLine {
|
||||
// If the marker is at the very end of the line, the marker will be a "^"
|
||||
// character that extends one column past the end of the line. In this case
|
||||
// we should reserve a column at the end so the marker doesn't wrap.
|
||||
width -= 1
|
||||
}
|
||||
if len(lineText) > width {
|
||||
// Try to center the error
|
||||
sliceStart := (markerStart + markerEnd - width) / 2
|
||||
if sliceStart > markerStart-width/5 {
|
||||
sliceStart = markerStart - width/5
|
||||
}
|
||||
if sliceStart < 0 {
|
||||
sliceStart = 0
|
||||
}
|
||||
if sliceStart > len(lineText)-width {
|
||||
sliceStart = len(lineText) - width
|
||||
}
|
||||
sliceEnd := sliceStart + width
|
||||
|
||||
// Slice the line
|
||||
slicedLine := lineText[sliceStart:sliceEnd]
|
||||
markerStart -= sliceStart
|
||||
markerEnd -= sliceStart
|
||||
if markerStart < 0 {
|
||||
markerStart = 0
|
||||
}
|
||||
if markerEnd > len(slicedLine) {
|
||||
markerEnd = len(slicedLine)
|
||||
}
|
||||
|
||||
// Truncate the ends with "..."
|
||||
if len(slicedLine) > 3 && sliceStart > 0 {
|
||||
slicedLine = "..." + slicedLine[3:]
|
||||
if markerStart < 3 {
|
||||
markerStart = 3
|
||||
}
|
||||
}
|
||||
if len(slicedLine) > 3 && sliceEnd < len(lineText) {
|
||||
slicedLine = slicedLine[:len(slicedLine)-3] + "..."
|
||||
if markerEnd > len(slicedLine)-3 {
|
||||
markerEnd = len(slicedLine) - 3
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can compute the indent
|
||||
lineText = slicedLine
|
||||
indent = strings.Repeat(" ", estimateWidthInTerminal(lineText[:markerStart]))
|
||||
}
|
||||
|
||||
// If marker is still multi-character after clipping, make the marker wider
|
||||
if markerEnd-markerStart > 1 {
|
||||
marker = strings.Repeat("~", estimateWidthInTerminal(lineText[markerStart:markerEnd]))
|
||||
}
|
||||
|
||||
// Put a margin before the marker indent
|
||||
margin := marginWithLineText(maxMargin, loc.Line)
|
||||
|
||||
return MsgDetail{
|
||||
Path: loc.File.Select(pathStyle),
|
||||
Line: loc.Line,
|
||||
Column: loc.Column,
|
||||
|
||||
SourceBefore: margin + lineText[:markerStart],
|
||||
SourceMarked: lineText[markerStart:markerEnd],
|
||||
SourceAfter: lineText[markerEnd:],
|
||||
|
||||
Indent: indent,
|
||||
Marker: marker,
|
||||
Suggestion: loc.Suggestion,
|
||||
|
||||
ContentAfter: afterFirstLine,
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate the number of columns this string will take when printed
|
||||
func estimateWidthInTerminal(text string) int {
|
||||
// For now just assume each code point is one column. This is wrong but is
|
||||
// less wrong than assuming each code unit is one column.
|
||||
width := 0
|
||||
for text != "" {
|
||||
c, size := utf8.DecodeRuneInString(text)
|
||||
text = text[size:]
|
||||
|
||||
// Ignore the Zero Width No-Break Space character (UTF-8 BOM)
|
||||
if c != 0xFEFF {
|
||||
width++
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func renderTabStops(withTabs string, spacesPerTab int) string {
|
||||
if !strings.ContainsRune(withTabs, '\t') {
|
||||
return withTabs
|
||||
}
|
||||
|
||||
withoutTabs := strings.Builder{}
|
||||
count := 0
|
||||
|
||||
for _, c := range withTabs {
|
||||
if c == '\t' {
|
||||
spaces := spacesPerTab - count%spacesPerTab
|
||||
for i := 0; i < spaces; i++ {
|
||||
withoutTabs.WriteRune(' ')
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
withoutTabs.WriteRune(c)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return withoutTabs.String()
|
||||
}
|
||||
|
||||
func (log Log) AddError(tracker *LineColumnTracker, r Range, text string) {
|
||||
log.AddMsg(Msg{
|
||||
Kind: Error,
|
||||
|
||||
+71
-56
@@ -21,6 +21,7 @@ const (
|
||||
MsgID_JS_CallImportNamespace
|
||||
MsgID_JS_ClassNameWillThrow
|
||||
MsgID_JS_CommonJSVariableInESM
|
||||
MsgID_JS_ConfusingTypeScriptCast
|
||||
MsgID_JS_DeleteSuperProperty
|
||||
MsgID_JS_DirectEval
|
||||
MsgID_JS_DuplicateCase
|
||||
@@ -116,6 +117,8 @@ func StringToMsgIDs(str string, logLevel LogLevel, overrides map[MsgID]LogLevel)
|
||||
overrides[MsgID_JS_ClassNameWillThrow] = logLevel
|
||||
case "commonjs-variable-in-esm":
|
||||
overrides[MsgID_JS_CommonJSVariableInESM] = logLevel
|
||||
case "confusing-typescript-cast":
|
||||
overrides[MsgID_JS_ConfusingTypeScriptCast] = logLevel
|
||||
case "delete-super-property":
|
||||
overrides[MsgID_JS_DeleteSuperProperty] = logLevel
|
||||
case "direct-eval":
|
||||
@@ -227,132 +230,144 @@ func StringToMsgIDs(str string, logLevel LogLevel, overrides map[MsgID]LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// These are stored together to make it less accident-prone to edit them
|
||||
type msgIDInfo struct {
|
||||
name string
|
||||
vsID vsID
|
||||
}
|
||||
|
||||
func MsgIDToString(id MsgID) string {
|
||||
return msgIDToInfo(id).name
|
||||
}
|
||||
|
||||
func msgIDToInfo(id MsgID) msgIDInfo {
|
||||
switch id {
|
||||
// JS
|
||||
case MsgID_JS_AssertToWith:
|
||||
return "assert-to-with"
|
||||
return msgIDInfo{name: "assert-to-with", vsID: vsID_JS_AssertToWith}
|
||||
case MsgID_JS_AssertTypeJSON:
|
||||
return "assert-type-json"
|
||||
return msgIDInfo{name: "assert-type-json", vsID: vsID_JS_AssertTypeJSON}
|
||||
case MsgID_JS_AssignToConstant:
|
||||
return "assign-to-constant"
|
||||
return msgIDInfo{name: "assign-to-constant", vsID: vsID_JS_AssignToConstant}
|
||||
case MsgID_JS_AssignToDefine:
|
||||
return "assign-to-define"
|
||||
return msgIDInfo{name: "assign-to-define", vsID: vsID_JS_AssignToDefine}
|
||||
case MsgID_JS_AssignToImport:
|
||||
return "assign-to-import"
|
||||
return msgIDInfo{name: "assign-to-import", vsID: vsID_JS_AssignToImport}
|
||||
case MsgID_JS_BigInt:
|
||||
return "bigint"
|
||||
return msgIDInfo{name: "bigint", vsID: vsID_JS_BigInt}
|
||||
case MsgID_JS_CallImportNamespace:
|
||||
return "call-import-namespace"
|
||||
return msgIDInfo{name: "call-import-namespace", vsID: vsID_JS_CallImportNamespace}
|
||||
case MsgID_JS_ClassNameWillThrow:
|
||||
return "class-name-will-throw"
|
||||
return msgIDInfo{name: "class-name-will-throw", vsID: vsID_JS_ClassNameWillThrow}
|
||||
case MsgID_JS_CommonJSVariableInESM:
|
||||
return "commonjs-variable-in-esm"
|
||||
return msgIDInfo{name: "commonjs-variable-in-esm", vsID: vsID_JS_CommonJSVariableInESM}
|
||||
case MsgID_JS_ConfusingTypeScriptCast:
|
||||
return msgIDInfo{name: "confusing-typescript-cast", vsID: vsID_JS_ConfusingTypeScriptCast}
|
||||
case MsgID_JS_DeleteSuperProperty:
|
||||
return "delete-super-property"
|
||||
return msgIDInfo{name: "delete-super-property", vsID: vsID_JS_DeleteSuperProperty}
|
||||
case MsgID_JS_DirectEval:
|
||||
return "direct-eval"
|
||||
return msgIDInfo{name: "direct-eval", vsID: vsID_JS_DirectEval}
|
||||
case MsgID_JS_DuplicateCase:
|
||||
return "duplicate-case"
|
||||
return msgIDInfo{name: "duplicate-case", vsID: vsID_JS_DuplicateCase}
|
||||
case MsgID_JS_DuplicateClassMember:
|
||||
return "duplicate-class-member"
|
||||
return msgIDInfo{name: "duplicate-class-member", vsID: vsID_JS_DuplicateClassMember}
|
||||
case MsgID_JS_DuplicateObjectKey:
|
||||
return "duplicate-object-key"
|
||||
return msgIDInfo{name: "duplicate-object-key", vsID: vsID_JS_DuplicateObjectKey}
|
||||
case MsgID_JS_EmptyImportMeta:
|
||||
return "empty-import-meta"
|
||||
return msgIDInfo{name: "empty-import-meta", vsID: vsID_JS_EmptyImportMeta}
|
||||
case MsgID_JS_EqualsNaN:
|
||||
return "equals-nan"
|
||||
return msgIDInfo{name: "equals-nan", vsID: vsID_JS_EqualsNaN}
|
||||
case MsgID_JS_EqualsNegativeZero:
|
||||
return "equals-negative-zero"
|
||||
return msgIDInfo{name: "equals-negative-zero", vsID: vsID_JS_EqualsNegativeZero}
|
||||
case MsgID_JS_EqualsNewObject:
|
||||
return "equals-new-object"
|
||||
return msgIDInfo{name: "equals-new-object", vsID: vsID_JS_EqualsNewObject}
|
||||
case MsgID_JS_HTMLCommentInJS:
|
||||
return "html-comment-in-js"
|
||||
return msgIDInfo{name: "html-comment-in-js", vsID: vsID_JS_HTMLCommentInJS}
|
||||
case MsgID_JS_ImpossibleTypeof:
|
||||
return "impossible-typeof"
|
||||
return msgIDInfo{name: "impossible-typeof", vsID: vsID_JS_ImpossibleTypeof}
|
||||
case MsgID_JS_IndirectRequire:
|
||||
return "indirect-require"
|
||||
return msgIDInfo{name: "indirect-require", vsID: vsID_JS_IndirectRequire}
|
||||
case MsgID_JS_PrivateNameWillThrow:
|
||||
return "private-name-will-throw"
|
||||
return msgIDInfo{name: "private-name-will-throw", vsID: vsID_JS_PrivateNameWillThrow}
|
||||
case MsgID_JS_SemicolonAfterReturn:
|
||||
return "semicolon-after-return"
|
||||
return msgIDInfo{name: "semicolon-after-return", vsID: vsID_JS_SemicolonAfterReturn}
|
||||
case MsgID_JS_SuspiciousBooleanNot:
|
||||
return "suspicious-boolean-not"
|
||||
return msgIDInfo{name: "suspicious-boolean-not", vsID: vsID_JS_SuspiciousBooleanNot}
|
||||
case MsgID_JS_SuspiciousDefine:
|
||||
return "suspicious-define"
|
||||
return msgIDInfo{name: "suspicious-define", vsID: vsID_JS_SuspiciousDefine}
|
||||
case MsgID_JS_SuspiciousLogicalOperator:
|
||||
return "suspicious-logical-operator"
|
||||
return msgIDInfo{name: "suspicious-logical-operator", vsID: vsID_JS_SuspiciousLogicalOperator}
|
||||
case MsgID_JS_SuspiciousNullishCoalescing:
|
||||
return "suspicious-nullish-coalescing"
|
||||
return msgIDInfo{name: "suspicious-nullish-coalescing", vsID: vsID_JS_SuspiciousNullishCoalescing}
|
||||
case MsgID_JS_ThisIsUndefinedInESM:
|
||||
return "this-is-undefined-in-esm"
|
||||
return msgIDInfo{name: "this-is-undefined-in-esm", vsID: vsID_JS_ThisIsUndefinedInESM}
|
||||
case MsgID_JS_UnsupportedDynamicImport:
|
||||
return "unsupported-dynamic-import"
|
||||
return msgIDInfo{name: "unsupported-dynamic-import", vsID: vsID_JS_UnsupportedDynamicImport}
|
||||
case MsgID_JS_UnsupportedJSXComment:
|
||||
return "unsupported-jsx-comment"
|
||||
return msgIDInfo{name: "unsupported-jsx-comment", vsID: vsID_JS_UnsupportedJSXComment}
|
||||
case MsgID_JS_UnsupportedRegExp:
|
||||
return "unsupported-regexp"
|
||||
return msgIDInfo{name: "unsupported-regexp", vsID: vsID_JS_UnsupportedRegExp}
|
||||
case MsgID_JS_UnsupportedRequireCall:
|
||||
return "unsupported-require-call"
|
||||
return msgIDInfo{name: "unsupported-require-call", vsID: vsID_JS_UnsupportedRequireCall}
|
||||
|
||||
// CSS
|
||||
case MsgID_CSS_CSSSyntaxError:
|
||||
return "css-syntax-error"
|
||||
return msgIDInfo{name: "css-syntax-error", vsID: vsID_CSS_CSSSyntaxError}
|
||||
case MsgID_CSS_InvalidAtCharset:
|
||||
return "invalid-@charset"
|
||||
return msgIDInfo{name: "invalid-@charset", vsID: vsID_CSS_InvalidAtCharset}
|
||||
case MsgID_CSS_InvalidAtImport:
|
||||
return "invalid-@import"
|
||||
return msgIDInfo{name: "invalid-@import", vsID: vsID_CSS_InvalidAtImport}
|
||||
case MsgID_CSS_InvalidAtLayer:
|
||||
return "invalid-@layer"
|
||||
return msgIDInfo{name: "invalid-@layer", vsID: vsID_CSS_InvalidAtLayer}
|
||||
case MsgID_CSS_InvalidCalc:
|
||||
return "invalid-calc"
|
||||
return msgIDInfo{name: "invalid-calc", vsID: vsID_CSS_InvalidCalc}
|
||||
case MsgID_CSS_JSCommentInCSS:
|
||||
return "js-comment-in-css"
|
||||
return msgIDInfo{name: "js-comment-in-css", vsID: vsID_CSS_JSCommentInCSS}
|
||||
case MsgID_CSS_UndefinedComposesFrom:
|
||||
return "undefined-composes-from"
|
||||
return msgIDInfo{name: "undefined-composes-from", vsID: vsID_CSS_UndefinedComposesFrom}
|
||||
case MsgID_CSS_UnsupportedAtCharset:
|
||||
return "unsupported-@charset"
|
||||
return msgIDInfo{name: "unsupported-@charset", vsID: vsID_CSS_UnsupportedAtCharset}
|
||||
case MsgID_CSS_UnsupportedAtNamespace:
|
||||
return "unsupported-@namespace"
|
||||
return msgIDInfo{name: "unsupported-@namespace", vsID: vsID_CSS_UnsupportedAtNamespace}
|
||||
case MsgID_CSS_UnsupportedCSSProperty:
|
||||
return "unsupported-css-property"
|
||||
return msgIDInfo{name: "unsupported-css-property", vsID: vsID_CSS_UnsupportedCSSProperty}
|
||||
case MsgID_CSS_UnsupportedCSSNesting:
|
||||
return "unsupported-css-nesting"
|
||||
return msgIDInfo{name: "unsupported-css-nesting", vsID: vsID_CSS_UnsupportedCSSNesting}
|
||||
|
||||
// Bundler
|
||||
case MsgID_Bundler_AmbiguousReexport:
|
||||
return "ambiguous-reexport"
|
||||
return msgIDInfo{name: "ambiguous-reexport", vsID: vsID_Bundler_AmbiguousReexport}
|
||||
case MsgID_Bundler_DifferentPathCase:
|
||||
return "different-path-case"
|
||||
return msgIDInfo{name: "different-path-case", vsID: vsID_Bundler_DifferentPathCase}
|
||||
case MsgID_Bundler_EmptyGlob:
|
||||
return "empty-glob"
|
||||
return msgIDInfo{name: "empty-glob", vsID: vsID_Bundler_EmptyGlob}
|
||||
case MsgID_Bundler_IgnoredBareImport:
|
||||
return "ignored-bare-import"
|
||||
return msgIDInfo{name: "ignored-bare-import", vsID: vsID_Bundler_IgnoredBareImport}
|
||||
case MsgID_Bundler_IgnoredDynamicImport:
|
||||
return "ignored-dynamic-import"
|
||||
return msgIDInfo{name: "ignored-dynamic-import", vsID: vsID_Bundler_IgnoredDynamicImport}
|
||||
case MsgID_Bundler_ImportIsUndefined:
|
||||
return "import-is-undefined"
|
||||
return msgIDInfo{name: "import-is-undefined", vsID: vsID_Bundler_ImportIsUndefined}
|
||||
case MsgID_Bundler_RequireResolveNotExternal:
|
||||
return "require-resolve-not-external"
|
||||
return msgIDInfo{name: "require-resolve-not-external", vsID: vsID_Bundler_RequireResolveNotExternal}
|
||||
|
||||
// Source maps
|
||||
case MsgID_SourceMap_InvalidSourceMappings:
|
||||
return "invalid-source-mappings"
|
||||
return msgIDInfo{name: "invalid-source-mappings", vsID: vsID_SourceMap_InvalidSourceMappings}
|
||||
case MsgID_SourceMap_MissingSourceMap:
|
||||
return "missing-source-map"
|
||||
return msgIDInfo{name: "missing-source-map", vsID: vsID_SourceMap_MissingSourceMap}
|
||||
case MsgID_SourceMap_UnsupportedSourceMapComment:
|
||||
return "unsupported-source-map-comment"
|
||||
return msgIDInfo{name: "unsupported-source-map-comment", vsID: vsID_SourceMap_UnsupportedSourceMapComment}
|
||||
|
||||
default:
|
||||
if id >= MsgID_PackageJSON_FIRST && id <= MsgID_PackageJSON_LAST {
|
||||
return "package.json"
|
||||
return msgIDInfo{name: "package.json", vsID: vsID_PackageJSON}
|
||||
}
|
||||
if id >= MsgID_TSConfigJSON_FIRST && id <= MsgID_TSConfigJSON_LAST {
|
||||
return "tsconfig.json"
|
||||
return msgIDInfo{name: "tsconfig.json", vsID: vsID_TSConfigJSON}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
return msgIDInfo{}
|
||||
}
|
||||
|
||||
// Some message IDs are more diverse internally than externally (in case we
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type LogStyle int8
|
||||
|
||||
const (
|
||||
StyleDefault LogStyle = iota
|
||||
StyleClang
|
||||
StyleVisualStudio
|
||||
)
|
||||
|
||||
func linkifyText(text string, underline string, reset string) string {
|
||||
if underline == "" {
|
||||
return text
|
||||
}
|
||||
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
return text
|
||||
}
|
||||
|
||||
sb := strings.Builder{}
|
||||
for {
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
end := strings.IndexByte(text[https:], ' ')
|
||||
if end == -1 {
|
||||
end = len(text)
|
||||
} else {
|
||||
end += https
|
||||
}
|
||||
|
||||
// Remove trailing punctuation
|
||||
if end > https {
|
||||
switch text[end-1] {
|
||||
case '.', ',', '?', '!', ')', ']', '}':
|
||||
end--
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(text[:https])
|
||||
sb.WriteString(underline)
|
||||
sb.WriteString(text[https:end])
|
||||
sb.WriteString(reset)
|
||||
text = text[end:]
|
||||
}
|
||||
|
||||
sb.WriteString(text)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func wrapWordsInString(text string, width int) []string {
|
||||
runs := []string{}
|
||||
|
||||
outer:
|
||||
for text != "" {
|
||||
i := 0
|
||||
x := 0
|
||||
wordEndI := 0
|
||||
|
||||
// Skip over any leading spaces
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
|
||||
// Find out how many words will fit in this run
|
||||
for i < len(text) {
|
||||
oldWordEndI := wordEndI
|
||||
wordStartI := i
|
||||
|
||||
// Find the end of the word
|
||||
for i < len(text) {
|
||||
c, width := utf8.DecodeRuneInString(text[i:])
|
||||
if c == ' ' {
|
||||
break
|
||||
}
|
||||
i += width
|
||||
x += 1 // Naively assume that each unicode code point is a single column
|
||||
}
|
||||
wordEndI = i
|
||||
|
||||
// Split into a new run if this isn't the first word in the run and the end is past the width
|
||||
if wordStartI > 0 && x > width {
|
||||
runs = append(runs, text[:oldWordEndI])
|
||||
text = text[wordStartI:]
|
||||
continue outer
|
||||
}
|
||||
|
||||
// Skip over any spaces after the word
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, this is the last run (i.e. everything fits)
|
||||
break
|
||||
}
|
||||
|
||||
// Remove any trailing spaces on the last run
|
||||
for len(text) > 0 && text[len(text)-1] == ' ' {
|
||||
text = text[:len(text)-1]
|
||||
}
|
||||
runs = append(runs, text)
|
||||
return runs
|
||||
}
|
||||
|
||||
type formattedLineSpan struct {
|
||||
firstLineBefore string
|
||||
firstLineMarked string
|
||||
firstLineAfter string
|
||||
afterFirstLine string
|
||||
marker string
|
||||
indent string
|
||||
}
|
||||
|
||||
func formatLineSpan(lineText string, line int, column int, length int, widthLimit int) formattedLineSpan {
|
||||
endOfFirstLine := len(lineText)
|
||||
|
||||
// Note: This uses "IndexByte" because Go implements this with SIMD, which
|
||||
// can matter a lot for really long lines. Some people pass huge >100mb
|
||||
// minified files as line text for the log message.
|
||||
if i := strings.IndexByte(lineText, '\n'); i >= 0 {
|
||||
endOfFirstLine = i
|
||||
}
|
||||
|
||||
firstLine := lineText[:endOfFirstLine]
|
||||
afterFirstLine := lineText[endOfFirstLine:]
|
||||
if afterFirstLine != "" && !strings.HasSuffix(afterFirstLine, "\n") {
|
||||
afterFirstLine += "\n"
|
||||
}
|
||||
|
||||
// Clamp values in range
|
||||
if line < 0 {
|
||||
line = 0
|
||||
}
|
||||
if column < 0 {
|
||||
column = 0
|
||||
}
|
||||
if length < 0 {
|
||||
length = 0
|
||||
}
|
||||
if column > endOfFirstLine {
|
||||
column = endOfFirstLine
|
||||
}
|
||||
if length > endOfFirstLine-column {
|
||||
length = endOfFirstLine - column
|
||||
}
|
||||
|
||||
spacesPerTab := 2
|
||||
lineText = renderTabStops(firstLine, spacesPerTab)
|
||||
textUpToLoc := renderTabStops(firstLine[:column], spacesPerTab)
|
||||
markerStart := len(textUpToLoc)
|
||||
markerEnd := markerStart
|
||||
indent := strings.Repeat(" ", estimateWidthInTerminal(textUpToLoc))
|
||||
marker := "^"
|
||||
|
||||
// Extend markers to cover the full range of the error
|
||||
if length > 0 {
|
||||
markerEnd = len(renderTabStops(firstLine[:column+length], spacesPerTab))
|
||||
}
|
||||
|
||||
// Clip the marker to the bounds of the line
|
||||
if markerStart > len(lineText) {
|
||||
markerStart = len(lineText)
|
||||
}
|
||||
if markerEnd > len(lineText) {
|
||||
markerEnd = len(lineText)
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
|
||||
// Trim the line to fit the terminal width
|
||||
width := widthLimit
|
||||
if width < 1 {
|
||||
width = 1
|
||||
}
|
||||
if column == endOfFirstLine {
|
||||
// If the marker is at the very end of the line, the marker will be a "^"
|
||||
// character that extends one column past the end of the line. In this case
|
||||
// we should reserve a column at the end so the marker doesn't wrap.
|
||||
width -= 1
|
||||
}
|
||||
if len(lineText) > width {
|
||||
// Try to center the error
|
||||
sliceStart := (markerStart + markerEnd - width) / 2
|
||||
if sliceStart > markerStart-width/5 {
|
||||
sliceStart = markerStart - width/5
|
||||
}
|
||||
if sliceStart < 0 {
|
||||
sliceStart = 0
|
||||
}
|
||||
if sliceStart > len(lineText)-width {
|
||||
sliceStart = len(lineText) - width
|
||||
}
|
||||
sliceEnd := sliceStart + width
|
||||
|
||||
// Slice the line
|
||||
slicedLine := lineText[sliceStart:sliceEnd]
|
||||
markerStart -= sliceStart
|
||||
markerEnd -= sliceStart
|
||||
if markerStart < 0 {
|
||||
markerStart = 0
|
||||
}
|
||||
if markerEnd > len(slicedLine) {
|
||||
markerEnd = len(slicedLine)
|
||||
}
|
||||
|
||||
// Truncate the ends with "..."
|
||||
if len(slicedLine) > 3 && sliceStart > 0 {
|
||||
slicedLine = "..." + slicedLine[3:]
|
||||
if markerStart < 3 {
|
||||
markerStart = 3
|
||||
}
|
||||
}
|
||||
if len(slicedLine) > 3 && sliceEnd < len(lineText) {
|
||||
slicedLine = slicedLine[:len(slicedLine)-3] + "..."
|
||||
if markerEnd > len(slicedLine)-3 {
|
||||
markerEnd = len(slicedLine) - 3
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can compute the indent
|
||||
lineText = slicedLine
|
||||
indent = strings.Repeat(" ", estimateWidthInTerminal(lineText[:markerStart]))
|
||||
}
|
||||
|
||||
// If marker is still multi-character after clipping, make the marker wider
|
||||
if markerEnd-markerStart > 1 {
|
||||
marker = strings.Repeat("~", estimateWidthInTerminal(lineText[markerStart:markerEnd]))
|
||||
}
|
||||
|
||||
return formattedLineSpan{
|
||||
firstLineBefore: lineText[:markerStart],
|
||||
firstLineMarked: lineText[markerStart:markerEnd],
|
||||
firstLineAfter: lineText[markerEnd:],
|
||||
afterFirstLine: afterFirstLine,
|
||||
marker: marker,
|
||||
indent: indent,
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate the number of columns this string will take when printed
|
||||
func estimateWidthInTerminal(text string) int {
|
||||
// For now just assume each code point is one column. This is wrong but is
|
||||
// less wrong than assuming each code unit is one column.
|
||||
width := 0
|
||||
for text != "" {
|
||||
c, size := utf8.DecodeRuneInString(text)
|
||||
text = text[size:]
|
||||
|
||||
// Ignore the Zero Width No-Break Space character (UTF-8 BOM)
|
||||
if c != 0xFEFF {
|
||||
width++
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func renderTabStops(withTabs string, spacesPerTab int) string {
|
||||
if !strings.ContainsRune(withTabs, '\t') {
|
||||
return withTabs
|
||||
}
|
||||
|
||||
withoutTabs := strings.Builder{}
|
||||
count := 0
|
||||
|
||||
for _, c := range withTabs {
|
||||
if c == '\t' {
|
||||
spaces := spacesPerTab - count%spacesPerTab
|
||||
for i := 0; i < spaces; i++ {
|
||||
withoutTabs.WriteRune(' ')
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
withoutTabs.WriteRune(c)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return withoutTabs.String()
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This log style is intended to help integrate esbuild with tools that expect
|
||||
// clang's output style. More information:
|
||||
//
|
||||
// - https://clang.llvm.org/docs/UsersManual.html#formatting-of-diagnostics
|
||||
// - https://clang.llvm.org/diagnostics.html
|
||||
func msgToStringClang(msg Msg, options OutputOptions, terminalInfo TerminalInfo) string {
|
||||
var text strings.Builder
|
||||
writeMsgDataClang(&text, msg.ID, msg.Data, options, terminalInfo, msg.Kind)
|
||||
for _, note := range msg.Notes {
|
||||
if note.Location != nil {
|
||||
writeMsgDataClang(&text, MsgID_None, note, options, terminalInfo, Note)
|
||||
}
|
||||
}
|
||||
return text.String()
|
||||
}
|
||||
|
||||
func writeMsgDataClang(text *strings.Builder, id MsgID, data MsgData, options OutputOptions, terminalInfo TerminalInfo, kind MsgKind) {
|
||||
var colors Colors
|
||||
if terminalInfo.UseColorEscapes {
|
||||
colors = TerminalColors
|
||||
}
|
||||
text.WriteString(colors.Bold)
|
||||
|
||||
if loc := data.Location; loc != nil {
|
||||
text.WriteString(loc.File.Select(options.PathStyle))
|
||||
|
||||
// Note: Need to adjust the column from 0-based to 1-based
|
||||
if loc.Line > 0 {
|
||||
text.WriteString(fmt.Sprintf(":%d:%d", loc.Line, loc.Column+1))
|
||||
}
|
||||
text.WriteString(": ")
|
||||
}
|
||||
|
||||
var kindColor string
|
||||
var kindText string
|
||||
switch kind {
|
||||
case Error:
|
||||
kindColor, kindText = colors.Red, "error"
|
||||
case Note:
|
||||
kindColor, kindText = colors.Dim, "note"
|
||||
default:
|
||||
kindColor, kindText = colors.Magenta, "warning"
|
||||
}
|
||||
|
||||
msgID := MsgIDToString(id)
|
||||
if msgID != "" {
|
||||
msgID = fmt.Sprintf(" [%s]", msgID)
|
||||
}
|
||||
|
||||
text.WriteString(fmt.Sprintf("%s%s:%s %s%s%s%s\n",
|
||||
kindColor, kindText, colors.Reset, colors.Bold, data.Text, msgID, colors.Reset))
|
||||
|
||||
if loc := data.Location; loc != nil && loc.Line > 0 {
|
||||
widthLimit := terminalInfo.Width
|
||||
if widthLimit < 1 {
|
||||
widthLimit = defaultTerminalWidth
|
||||
}
|
||||
|
||||
span := formatLineSpan(loc.LineText, loc.Line, loc.Column, loc.Length, widthLimit)
|
||||
text.WriteString(fmt.Sprintf("%s%s%s%s%s%s\n%s%s%s%s\n",
|
||||
colors.Dim, span.firstLineBefore, colors.Green, span.firstLineMarked, colors.Dim, span.firstLineAfter,
|
||||
span.indent, colors.Green, span.marker, colors.Reset,
|
||||
))
|
||||
|
||||
if loc.Suggestion != "" {
|
||||
text.WriteString(fmt.Sprintf("%s%s%s%s\n", colors.Green, span.indent, loc.Suggestion, colors.Reset))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func msgToStringDefault(msg Msg, options OutputOptions, terminalInfo TerminalInfo) string {
|
||||
// Format the message
|
||||
var text strings.Builder
|
||||
text.WriteString(msgString(options.IncludeSource, options.PathStyle, terminalInfo, msg.ID, msg.Kind, msg.Data, msg.PluginName))
|
||||
|
||||
// Format the notes
|
||||
var oldData MsgData
|
||||
for i, note := range msg.Notes {
|
||||
if options.IncludeSource && (i == 0 || strings.IndexByte(oldData.Text, '\n') >= 0 || oldData.Location != nil) {
|
||||
text.WriteString("\n")
|
||||
}
|
||||
text.WriteString(msgString(options.IncludeSource, options.PathStyle, terminalInfo, MsgID_None, Note, note, ""))
|
||||
oldData = note
|
||||
}
|
||||
|
||||
// Add extra spacing between messages if source code is present
|
||||
if options.IncludeSource {
|
||||
text.WriteString("\n")
|
||||
}
|
||||
return text.String()
|
||||
}
|
||||
|
||||
func marginWithLineText(maxMargin int, line int) string {
|
||||
number := fmt.Sprintf("%d", line)
|
||||
return fmt.Sprintf(" %s%s │ ", strings.Repeat(" ", maxMargin-len(number)), number)
|
||||
}
|
||||
|
||||
func emptyMarginText(maxMargin int, isLast bool) string {
|
||||
space := strings.Repeat(" ", maxMargin)
|
||||
if isLast {
|
||||
return fmt.Sprintf(" %s ╵ ", space)
|
||||
}
|
||||
return fmt.Sprintf(" %s │ ", space)
|
||||
}
|
||||
|
||||
func msgString(includeSource bool, pathStyle PathStyle, terminalInfo TerminalInfo, id MsgID, kind MsgKind, data MsgData, pluginName string) string {
|
||||
if !includeSource {
|
||||
if loc := data.Location; loc != nil {
|
||||
return fmt.Sprintf("%s: %s: %s\n", loc.File.Select(pathStyle), kind.String(), data.Text)
|
||||
}
|
||||
return fmt.Sprintf("%s: %s\n", kind.String(), data.Text)
|
||||
}
|
||||
|
||||
var colors Colors
|
||||
if terminalInfo.UseColorEscapes {
|
||||
colors = TerminalColors
|
||||
}
|
||||
|
||||
var iconColor string
|
||||
var kindColorBrackets string
|
||||
var kindColorText string
|
||||
|
||||
location := ""
|
||||
|
||||
if loc := data.Location; loc != nil {
|
||||
maxMargin := len(fmt.Sprintf("%d", loc.Line))
|
||||
margin := marginWithLineText(maxMargin, loc.Line)
|
||||
widthLimit := terminalInfo.Width
|
||||
if widthLimit < 1 {
|
||||
widthLimit = defaultTerminalWidth
|
||||
}
|
||||
widthLimit -= maxMargin + extraMarginChars
|
||||
|
||||
span := formatLineSpan(loc.LineText, loc.Line, loc.Column, loc.Length, widthLimit)
|
||||
path := loc.File.Select(pathStyle)
|
||||
|
||||
if loc.Suggestion != "" {
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s%s\n%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
path, loc.Line, loc.Column,
|
||||
colors.Dim, margin, span.firstLineBefore, colors.Green, span.firstLineMarked, colors.Dim, span.firstLineAfter,
|
||||
emptyMarginText(maxMargin, false), span.indent, colors.Green, span.marker, colors.Dim,
|
||||
emptyMarginText(maxMargin, true), span.indent, colors.Green, loc.Suggestion, colors.Reset,
|
||||
span.afterFirstLine,
|
||||
)
|
||||
} else {
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
path, loc.Line, loc.Column,
|
||||
colors.Dim, margin, span.firstLineBefore, colors.Green, span.firstLineMarked, colors.Dim, span.firstLineAfter,
|
||||
emptyMarginText(maxMargin, true), span.indent, colors.Green, span.marker, colors.Reset,
|
||||
span.afterFirstLine,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case Verbose:
|
||||
iconColor = colors.Cyan
|
||||
kindColorBrackets = colors.CyanBgCyan
|
||||
kindColorText = colors.CyanBgBlack
|
||||
|
||||
case Debug:
|
||||
iconColor = colors.Green
|
||||
kindColorBrackets = colors.GreenBgGreen
|
||||
kindColorText = colors.GreenBgWhite
|
||||
|
||||
case Info:
|
||||
iconColor = colors.Blue
|
||||
kindColorBrackets = colors.BlueBgBlue
|
||||
kindColorText = colors.BlueBgWhite
|
||||
|
||||
case Error:
|
||||
iconColor = colors.Red
|
||||
kindColorBrackets = colors.RedBgRed
|
||||
kindColorText = colors.RedBgWhite
|
||||
|
||||
case Warning:
|
||||
iconColor = colors.Yellow
|
||||
kindColorBrackets = colors.YellowBgYellow
|
||||
kindColorText = colors.YellowBgBlack
|
||||
|
||||
case Note:
|
||||
sb := strings.Builder{}
|
||||
|
||||
for _, line := range strings.Split(data.Text, "\n") {
|
||||
// Special-case word wrapping
|
||||
if wrapWidth := terminalInfo.Width; wrapWidth > 2 {
|
||||
if !data.DisableMaximumWidth && wrapWidth > 100 {
|
||||
wrapWidth = 100 // Enforce a maximum paragraph width for readability
|
||||
}
|
||||
for _, run := range wrapWordsInString(line, wrapWidth-2) {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(linkifyText(run, colors.Underline, colors.Reset))
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, just write an indented line
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(linkifyText(line, colors.Underline, colors.Reset))
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
sb.WriteString(location)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
if pluginName != "" {
|
||||
pluginName = fmt.Sprintf(" %s%s[plugin %s]%s", colors.Bold, colors.Magenta, pluginName, colors.Reset)
|
||||
}
|
||||
|
||||
msgID := MsgIDToString(id)
|
||||
if msgID != "" {
|
||||
msgID = fmt.Sprintf(" [%s]", msgID)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s %s[%s%s%s]%s %s%s%s%s%s\n%s",
|
||||
iconColor, kind.Icon(),
|
||||
kindColorBrackets, kindColorText, kind.String(), kindColorBrackets, colors.Reset,
|
||||
colors.Bold, data.Text, colors.Reset, pluginName, msgID,
|
||||
location,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This log style is intended to help integrate esbuild with Visual Studio's
|
||||
// built-in problem matcher. More information:
|
||||
//
|
||||
// - https://learn.microsoft.com/en-us/cpp/build/formatting-the-output-of-a-custom-build-step-or-build-event
|
||||
// - https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-diagnostic-format-for-tasks
|
||||
func msgToStringVisualStudio(msg Msg, options OutputOptions, terminalInfo TerminalInfo) string {
|
||||
var text strings.Builder
|
||||
|
||||
// Write the origin
|
||||
if loc := msg.Data.Location; loc != nil {
|
||||
// Potentially this needs to always be an absolute path?
|
||||
text.WriteString(loc.File.Abs)
|
||||
|
||||
// Note: Need to adjust the column from 0-based to 1-based
|
||||
if loc.Line > 0 {
|
||||
text.WriteString(fmt.Sprintf("(%d,%d)", loc.Line, loc.Column+1))
|
||||
}
|
||||
text.WriteString(": ")
|
||||
} else {
|
||||
// If there is no file, then we must write a tool name
|
||||
text.WriteString("esbuild: ")
|
||||
}
|
||||
|
||||
// The code appears to be required, so make something up
|
||||
code := fmt.Sprintf("ES%04d", msgIDToInfo(msg.ID).vsID)
|
||||
|
||||
// The only valid options for the category seem to be "error" and "warning"
|
||||
if msg.Kind == Error {
|
||||
text.WriteString(fmt.Sprintf("error %s: ", code))
|
||||
} else {
|
||||
text.WriteString(fmt.Sprintf("warning %s: ", code))
|
||||
}
|
||||
|
||||
text.WriteString(msg.Data.Text)
|
||||
text.WriteByte('\n')
|
||||
return text.String()
|
||||
}
|
||||
|
||||
type vsID = uint8
|
||||
|
||||
// These are separate from "MsgID" because they are external and "MsgID" is
|
||||
// internal. Don't ever delete or reorder one of these. Only append to them.
|
||||
const (
|
||||
vsID_JS_AssertToWith = 1
|
||||
vsID_JS_AssertTypeJSON = 2
|
||||
vsID_JS_AssignToConstant = 3
|
||||
vsID_JS_AssignToDefine = 4
|
||||
vsID_JS_AssignToImport = 5
|
||||
vsID_JS_BigInt = 6
|
||||
vsID_JS_CallImportNamespace = 7
|
||||
vsID_JS_ClassNameWillThrow = 8
|
||||
vsID_JS_CommonJSVariableInESM = 9
|
||||
vsID_JS_ConfusingTypeScriptCast = 10
|
||||
vsID_JS_DeleteSuperProperty = 11
|
||||
vsID_JS_DirectEval = 12
|
||||
vsID_JS_DuplicateCase = 13
|
||||
vsID_JS_DuplicateClassMember = 14
|
||||
vsID_JS_DuplicateObjectKey = 15
|
||||
vsID_JS_EmptyImportMeta = 16
|
||||
vsID_JS_EqualsNaN = 17
|
||||
vsID_JS_EqualsNegativeZero = 18
|
||||
vsID_JS_EqualsNewObject = 19
|
||||
vsID_JS_HTMLCommentInJS = 20
|
||||
vsID_JS_ImpossibleTypeof = 21
|
||||
vsID_JS_IndirectRequire = 22
|
||||
vsID_JS_PrivateNameWillThrow = 23
|
||||
vsID_JS_SemicolonAfterReturn = 24
|
||||
vsID_JS_SuspiciousBooleanNot = 25
|
||||
vsID_JS_SuspiciousDefine = 26
|
||||
vsID_JS_SuspiciousLogicalOperator = 27
|
||||
vsID_JS_SuspiciousNullishCoalescing = 28
|
||||
vsID_JS_ThisIsUndefinedInESM = 29
|
||||
vsID_JS_UnsupportedDynamicImport = 30
|
||||
vsID_JS_UnsupportedJSXComment = 31
|
||||
vsID_JS_UnsupportedRegExp = 32
|
||||
vsID_JS_UnsupportedRequireCall = 33
|
||||
vsID_CSS_CSSSyntaxError = 34
|
||||
vsID_CSS_InvalidAtCharset = 35
|
||||
vsID_CSS_InvalidAtImport = 36
|
||||
vsID_CSS_InvalidAtLayer = 37
|
||||
vsID_CSS_InvalidCalc = 38
|
||||
vsID_CSS_JSCommentInCSS = 39
|
||||
vsID_CSS_UndefinedComposesFrom = 40
|
||||
vsID_CSS_UnsupportedAtCharset = 41
|
||||
vsID_CSS_UnsupportedAtNamespace = 42
|
||||
vsID_CSS_UnsupportedCSSProperty = 43
|
||||
vsID_CSS_UnsupportedCSSNesting = 44
|
||||
vsID_Bundler_AmbiguousReexport = 45
|
||||
vsID_Bundler_DifferentPathCase = 46
|
||||
vsID_Bundler_EmptyGlob = 47
|
||||
vsID_Bundler_IgnoredBareImport = 48
|
||||
vsID_Bundler_IgnoredDynamicImport = 49
|
||||
vsID_Bundler_ImportIsUndefined = 50
|
||||
vsID_Bundler_RequireResolveNotExternal = 51
|
||||
vsID_SourceMap_InvalidSourceMappings = 52
|
||||
vsID_SourceMap_MissingSourceMap = 53
|
||||
vsID_SourceMap_UnsupportedSourceMapComment = 54
|
||||
vsID_PackageJSON = 55
|
||||
vsID_TSConfigJSON = 56
|
||||
)
|
||||
@@ -221,7 +221,7 @@ func ParseTSConfigJSON(
|
||||
switch lowerValue {
|
||||
case "es3", "es5", "es6", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020", "es2021":
|
||||
result.Settings.Target = config.TSTargetBelowES2022
|
||||
case "es2022", "es2023", "es2024", "es2025", "esnext":
|
||||
case "es2022", "es2023", "es2024", "es2025", "es2026", "esnext":
|
||||
result.Settings.Target = config.TSTargetAtOrAboveES2022
|
||||
default:
|
||||
ok = false
|
||||
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
declare module "https://deno.land/x/[email protected]/mod.ts" {
|
||||
export function gunzip(input: Uint8Array): Uint8Array
|
||||
}
|
||||
|
||||
// This is used by "worker.ts"
|
||||
declare let onmessage: (event: any) => void
|
||||
-435
@@ -1,435 +0,0 @@
|
||||
import type * as types from "../shared/types"
|
||||
import * as common from "../shared/common"
|
||||
import * as ourselves from "./mod"
|
||||
import * as denoflate from "https://deno.land/x/[email protected]/mod.ts"
|
||||
|
||||
declare const ESBUILD_VERSION: string
|
||||
declare const ESBUILD_BINARY_HASHES: Record<string, string>
|
||||
|
||||
export let version = ESBUILD_VERSION
|
||||
|
||||
export let build: typeof types.build = (options: types.BuildOptions) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.build(options))
|
||||
|
||||
export const context: typeof types.context = (options: types.BuildOptions) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.context(options))
|
||||
|
||||
export const transform: typeof types.transform = (input: string | Uint8Array, options?: types.TransformOptions) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.transform(input, options))
|
||||
|
||||
export const formatMessages: typeof types.formatMessages = (messages, options) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.formatMessages(messages, options))
|
||||
|
||||
export const analyzeMetafile: typeof types.analyzeMetafile = (metafile, options) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.analyzeMetafile(metafile, options))
|
||||
|
||||
export const buildSync: typeof types.buildSync = () => {
|
||||
throw new Error(`The "buildSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const transformSync: typeof types.transformSync = () => {
|
||||
throw new Error(`The "transformSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const formatMessagesSync: typeof types.formatMessagesSync = () => {
|
||||
throw new Error(`The "formatMessagesSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const analyzeMetafileSync: typeof types.analyzeMetafileSync = () => {
|
||||
throw new Error(`The "analyzeMetafileSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const stop = async () => {
|
||||
if (stopService) await stopService()
|
||||
}
|
||||
|
||||
let initializeWasCalled = false
|
||||
|
||||
export const initialize: typeof types.initialize = async (options) => {
|
||||
options = common.validateInitializeOptions(options || {})
|
||||
if (options.wasmURL) throw new Error(`The "wasmURL" option only works in the browser`)
|
||||
if (options.wasmModule) throw new Error(`The "wasmModule" option only works in the browser`)
|
||||
if (options.worker) throw new Error(`The "worker" option only works in the browser`)
|
||||
if (initializeWasCalled) throw new Error('Cannot call "initialize" more than once')
|
||||
await ensureServiceIsRunning()
|
||||
initializeWasCalled = true
|
||||
}
|
||||
|
||||
async function binaryIntegrityCheck(pkg: string, subpath: string, bytes: Uint8Array): Promise<void> {
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes as Uint8Array<ArrayBuffer>)
|
||||
const hash = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
const key = `${pkg}/${subpath}`
|
||||
const expected = ESBUILD_BINARY_HASHES[key]
|
||||
if (!expected) throw new Error(`Missing hash for "${key}"`)
|
||||
if (hash !== expected) throw new Error(`"${hash.slice(0, 8)}..." doesn't match "${expected.slice(0, 8)}..." for "${pkg}"`)
|
||||
}
|
||||
|
||||
async function installFromNPM(name: string, subpath: string): Promise<string> {
|
||||
const { finalPath, finalDir } = getCachePath(name)
|
||||
try {
|
||||
await Deno.stat(finalPath)
|
||||
return finalPath
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
const npmRegistry = Deno.env.get("NPM_CONFIG_REGISTRY") || "https://registry.npmjs.org"
|
||||
const url = `${npmRegistry}/${name}/-/${name.replace("@esbuild/", "")}-${version}.tgz`
|
||||
const buffer = await fetch(url).then(r => r.arrayBuffer())
|
||||
const executable = extractFileFromTarGzip(new Uint8Array(buffer), subpath)
|
||||
await binaryIntegrityCheck(name, subpath, executable)
|
||||
|
||||
await Deno.mkdir(finalDir, {
|
||||
recursive: true,
|
||||
mode: 0o700, // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
})
|
||||
|
||||
await Deno.writeFile(finalPath, executable, { mode: 0o755 })
|
||||
return finalPath
|
||||
}
|
||||
|
||||
function getCachePath(name: string): { finalPath: string, finalDir: string } {
|
||||
let baseDir: string | undefined
|
||||
|
||||
switch (Deno.build.os) {
|
||||
case 'darwin':
|
||||
baseDir = Deno.env.get('HOME')
|
||||
if (baseDir) baseDir += '/Library/Caches'
|
||||
break
|
||||
|
||||
case 'windows':
|
||||
baseDir = Deno.env.get('LOCALAPPDATA')
|
||||
if (!baseDir) {
|
||||
baseDir = Deno.env.get('USERPROFILE')
|
||||
if (baseDir) baseDir += '/AppData/Local'
|
||||
}
|
||||
if (baseDir) baseDir += '/Cache'
|
||||
break
|
||||
|
||||
case 'linux':
|
||||
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
const xdg = Deno.env.get('XDG_CACHE_HOME')
|
||||
if (xdg && xdg[0] === '/') baseDir = xdg
|
||||
break
|
||||
}
|
||||
|
||||
if (!baseDir) {
|
||||
baseDir = Deno.env.get('HOME')
|
||||
if (baseDir) baseDir += '/.cache'
|
||||
}
|
||||
|
||||
if (!baseDir) throw new Error('Failed to find cache directory')
|
||||
const finalDir = baseDir + `/esbuild/bin`
|
||||
const finalPath = finalDir + `/${name.replace('/', '-')}@${version}`
|
||||
return { finalPath, finalDir }
|
||||
}
|
||||
|
||||
function extractFileFromTarGzip(buffer: Uint8Array, file: string): Uint8Array {
|
||||
try {
|
||||
buffer = denoflate.gunzip(buffer)
|
||||
} catch (err: any) {
|
||||
throw new Error(`Invalid gzip data in archive: ${err && err.message || err}`)
|
||||
}
|
||||
let str = (i: number, n: number) => String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, '')
|
||||
let offset = 0
|
||||
file = `package/${file}`
|
||||
while (offset < buffer.length) {
|
||||
let name = str(offset, 100)
|
||||
let size = parseInt(str(offset + 124, 12), 8)
|
||||
offset += 512
|
||||
if (!isNaN(size) && size >= 0) {
|
||||
if (name === file) return buffer.subarray(offset, offset + size)
|
||||
offset += (size + 511) & ~511
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find ${JSON.stringify(file)} in archive`)
|
||||
}
|
||||
|
||||
async function install(): Promise<string> {
|
||||
const overridePath = Deno.env.get('ESBUILD_BINARY_PATH')
|
||||
if (overridePath) return overridePath
|
||||
|
||||
const platformKey = Deno.build.target
|
||||
const knownWindowsPackages: Record<string, string> = {
|
||||
'x86_64-pc-windows-msvc': '@esbuild/win32-x64',
|
||||
}
|
||||
const knownUnixlikePackages: Record<string, string> = {
|
||||
// These are the only platforms that Deno supports
|
||||
'aarch64-apple-darwin': '@esbuild/darwin-arm64',
|
||||
'aarch64-unknown-linux-gnu': '@esbuild/linux-arm64',
|
||||
'x86_64-apple-darwin': '@esbuild/darwin-x64',
|
||||
'x86_64-unknown-linux-gnu': '@esbuild/linux-x64',
|
||||
|
||||
// These platforms are not supported by Deno
|
||||
'aarch64-linux-android': '@esbuild/android-arm64',
|
||||
'x86_64-unknown-freebsd': '@esbuild/freebsd-x64',
|
||||
"x86_64-alpine-linux-musl": '@esbuild/linux-x64',
|
||||
}
|
||||
|
||||
// Pick a package to install
|
||||
if (platformKey in knownWindowsPackages) {
|
||||
return await installFromNPM(knownWindowsPackages[platformKey], 'esbuild.exe')
|
||||
} else if (platformKey in knownUnixlikePackages) {
|
||||
return await installFromNPM(knownUnixlikePackages[platformKey], 'bin/esbuild')
|
||||
} else {
|
||||
throw new Error(`Unsupported platform: ${platformKey}`)
|
||||
}
|
||||
}
|
||||
|
||||
interface Service {
|
||||
build: typeof types.build
|
||||
context: typeof types.context
|
||||
transform: typeof types.transform
|
||||
formatMessages: typeof types.formatMessages
|
||||
analyzeMetafile: typeof types.analyzeMetafile
|
||||
}
|
||||
|
||||
let defaultWD = Deno.cwd()
|
||||
let longLivedService: Promise<Service> | undefined
|
||||
let stopService: (() => Promise<void>) | undefined
|
||||
|
||||
// Declare a common subprocess API for the two implementations below
|
||||
type SpawnFn = (cmd: string, options: {
|
||||
args: string[]
|
||||
stdin: 'piped' | 'inherit'
|
||||
stdout: 'piped' | 'inherit'
|
||||
stderr: 'inherit'
|
||||
}) => {
|
||||
write(bytes: Uint8Array): void
|
||||
read(): Promise<Uint8Array | null>
|
||||
close(): Promise<void> | void
|
||||
status(): Promise<{ code: number }>
|
||||
}
|
||||
|
||||
// Deno ≥1.40
|
||||
const spawnNew: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => {
|
||||
const child = new Deno.Command(cmd, {
|
||||
args,
|
||||
cwd: defaultWD,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
}).spawn()
|
||||
// Note: Need to check for "piped" in Deno ≥1.31.0 to avoid a crash
|
||||
const writer = stdin === 'piped' ? child.stdin.getWriter() : null
|
||||
const reader = stdout === 'piped' ? child.stdout.getReader() : null
|
||||
return {
|
||||
write: writer ? bytes => writer.write(bytes) : () => Promise.resolve(),
|
||||
read: reader ? () => reader.read().then(x => x.value || null) : () => Promise.resolve(null),
|
||||
close: async () => {
|
||||
// We can't call "kill()" because it doesn't seem to work. Tests will
|
||||
// still fail with "A child process was opened during the test, but not
|
||||
// closed during the test" even though we kill the child process.
|
||||
//
|
||||
// And we can't call both "writer.close()" and "kill()" because then
|
||||
// there's a race as the child process exits when stdin is closed, and
|
||||
// "kill()" fails when the child process has already been killed.
|
||||
//
|
||||
// So instead we just call "writer.close()" and then hope that this
|
||||
// causes the child process to exit. It won't work if the stdin consumer
|
||||
// thread in the child process is hung or busy, but that may be the best
|
||||
// we can do.
|
||||
//
|
||||
// See this for more info: https://github.com/evanw/esbuild/pull/3611
|
||||
if (writer) await writer.close()
|
||||
if (reader) await reader.cancel()
|
||||
|
||||
// Wait for the process to exit. The new "kill()" API doesn't flag the
|
||||
// process as having exited because processes can technically ignore the
|
||||
// kill signal. Without this, Deno will fail tests that use esbuild with
|
||||
// an error because the test spawned a process but didn't wait for it.
|
||||
await child.status
|
||||
},
|
||||
status: () => child.status,
|
||||
}
|
||||
}
|
||||
|
||||
// Deno <1.40
|
||||
const spawnOld: SpawnFn = (cmd, { args, stdin, stdout, stderr }) => {
|
||||
const child = Deno.run({
|
||||
cmd: [cmd].concat(args),
|
||||
cwd: defaultWD,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
const stdoutBuffer = new Uint8Array(4 * 1024 * 1024)
|
||||
let writeQueue: Uint8Array[] = []
|
||||
let isQueueLocked = false
|
||||
|
||||
// We need to keep calling "write()" until it actually writes the data
|
||||
const startWriteFromQueueWorker = () => {
|
||||
if (isQueueLocked || writeQueue.length === 0) return
|
||||
isQueueLocked = true
|
||||
child.stdin!.write(writeQueue[0]).then(bytesWritten => {
|
||||
isQueueLocked = false
|
||||
if (bytesWritten === writeQueue[0].length) writeQueue.shift()
|
||||
else writeQueue[0] = writeQueue[0].subarray(bytesWritten)
|
||||
startWriteFromQueueWorker()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
write: bytes => {
|
||||
writeQueue.push(bytes)
|
||||
startWriteFromQueueWorker()
|
||||
},
|
||||
read: () => child.stdout!.read(stdoutBuffer).then(n => n === null ? null : stdoutBuffer.subarray(0, n)),
|
||||
close: () => {
|
||||
child.stdin!.close()
|
||||
child.stdout!.close()
|
||||
child.close()
|
||||
},
|
||||
status: () => child.status(),
|
||||
}
|
||||
}
|
||||
|
||||
// This is a shim for "Deno.run" for newer versions of Deno
|
||||
const spawn: SpawnFn = Deno.Command ? spawnNew : spawnOld
|
||||
|
||||
const ensureServiceIsRunning = (): Promise<Service> => {
|
||||
if (!longLivedService) {
|
||||
longLivedService = (async (): Promise<Service> => {
|
||||
const binPath = await install()
|
||||
const isTTY = Deno.stderr.isTerminal
|
||||
? Deno.stderr.isTerminal() // Deno ≥1.40
|
||||
: Deno.isatty(Deno.stderr.rid) // Deno <1.40
|
||||
|
||||
const child = spawn(binPath, {
|
||||
args: [`--service=${version}`],
|
||||
stdin: 'piped',
|
||||
stdout: 'piped',
|
||||
stderr: 'inherit',
|
||||
})
|
||||
|
||||
stopService = async () => {
|
||||
// Close all resources related to the subprocess.
|
||||
await child.close()
|
||||
initializeWasCalled = false
|
||||
longLivedService = undefined
|
||||
stopService = undefined
|
||||
}
|
||||
|
||||
const { readFromStdout, afterClose, service } = common.createChannel({
|
||||
writeToStdin(bytes) {
|
||||
child.write(bytes)
|
||||
},
|
||||
isSync: false,
|
||||
hasFS: true,
|
||||
esbuild: ourselves,
|
||||
})
|
||||
|
||||
const readMoreStdout = () => child.read().then(buffer => {
|
||||
if (buffer === null) {
|
||||
afterClose(null)
|
||||
} else {
|
||||
readFromStdout(buffer)
|
||||
readMoreStdout()
|
||||
}
|
||||
}).catch(e => {
|
||||
if (e instanceof Deno.errors.Interrupted || e instanceof Deno.errors.BadResource) {
|
||||
// ignore the error if read was interrupted (stdout was closed)
|
||||
afterClose(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
})
|
||||
readMoreStdout()
|
||||
|
||||
return {
|
||||
build: (options: types.BuildOptions) =>
|
||||
new Promise<types.BuildResult>((resolve, reject) => {
|
||||
service.buildOrContext({
|
||||
callName: 'build',
|
||||
refs: null,
|
||||
options,
|
||||
isTTY,
|
||||
defaultWD,
|
||||
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildResult),
|
||||
})
|
||||
}),
|
||||
|
||||
context: (options: types.BuildOptions) =>
|
||||
new Promise<types.BuildContext>((resolve, reject) =>
|
||||
service.buildOrContext({
|
||||
callName: 'context',
|
||||
refs: null,
|
||||
options,
|
||||
isTTY,
|
||||
defaultWD,
|
||||
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildContext),
|
||||
})),
|
||||
|
||||
transform: (input: string | Uint8Array, options?: types.TransformOptions) =>
|
||||
new Promise<types.TransformResult>((resolve, reject) =>
|
||||
service.transform({
|
||||
callName: 'transform',
|
||||
refs: null,
|
||||
input,
|
||||
options: options || {},
|
||||
isTTY,
|
||||
fs: {
|
||||
readFile(tempFile, callback) {
|
||||
Deno.readFile(tempFile).then(
|
||||
bytes => {
|
||||
let text = new TextDecoder().decode(bytes)
|
||||
try {
|
||||
Deno.remove(tempFile)
|
||||
} catch (e) {
|
||||
}
|
||||
callback(null, text)
|
||||
},
|
||||
err => callback(err, null),
|
||||
)
|
||||
},
|
||||
writeFile(contents, callback) {
|
||||
Deno.makeTempFile().then(
|
||||
tempFile => Deno.writeFile(tempFile, typeof contents === 'string' ? new TextEncoder().encode(contents) : contents).then(
|
||||
() => callback(tempFile),
|
||||
() => callback(null)),
|
||||
() => callback(null))
|
||||
},
|
||||
},
|
||||
callback: (err, res) => err ? reject(err) : resolve(res!),
|
||||
})),
|
||||
|
||||
formatMessages: (messages, options) =>
|
||||
new Promise((resolve, reject) =>
|
||||
service.formatMessages({
|
||||
callName: 'formatMessages',
|
||||
refs: null,
|
||||
messages,
|
||||
options,
|
||||
callback: (err, res) => err ? reject(err) : resolve(res!),
|
||||
})),
|
||||
|
||||
analyzeMetafile: (metafile, options) =>
|
||||
new Promise((resolve, reject) =>
|
||||
service.analyzeMetafile({
|
||||
callName: 'analyzeMetafile',
|
||||
refs: null,
|
||||
metafile: typeof metafile === 'string' ? metafile : JSON.stringify(metafile),
|
||||
options,
|
||||
callback: (err, res) => err ? reject(err) : resolve(res!),
|
||||
})),
|
||||
}
|
||||
})()
|
||||
}
|
||||
return longLivedService
|
||||
}
|
||||
|
||||
// If we're called as the main script, forward the CLI to the underlying executable
|
||||
if (import.meta.main) {
|
||||
spawn(await install(), {
|
||||
args: Deno.args,
|
||||
stdin: 'inherit',
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
}).status().then(({ code }) => {
|
||||
Deno.exit(code)
|
||||
})
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
import type * as types from "../shared/types"
|
||||
import * as common from "../shared/common"
|
||||
import * as ourselves from "./wasm"
|
||||
|
||||
interface Go {
|
||||
_scheduledTimeouts: Map<number, ReturnType<typeof setTimeout>>
|
||||
}
|
||||
|
||||
declare const ESBUILD_VERSION: string
|
||||
declare let WEB_WORKER_SOURCE_CODE: string
|
||||
declare let WEB_WORKER_FUNCTION: (postMessage: (data: Uint8Array) => void) => (event: { data: Uint8Array | ArrayBuffer | WebAssembly.Module }) => Go
|
||||
|
||||
export let version = ESBUILD_VERSION
|
||||
|
||||
export let build: typeof types.build = (options: types.BuildOptions) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.build(options))
|
||||
|
||||
export const context: typeof types.context = (options: types.BuildOptions) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.context(options))
|
||||
|
||||
export const transform: typeof types.transform = (input: string | Uint8Array, options?: types.TransformOptions) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.transform(input, options))
|
||||
|
||||
export const formatMessages: typeof types.formatMessages = (messages, options) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.formatMessages(messages, options))
|
||||
|
||||
export const analyzeMetafile: typeof types.analyzeMetafile = (metafile, options) =>
|
||||
ensureServiceIsRunning().then(service =>
|
||||
service.analyzeMetafile(metafile, options))
|
||||
|
||||
export const buildSync: typeof types.buildSync = () => {
|
||||
throw new Error(`The "buildSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const transformSync: typeof types.transformSync = () => {
|
||||
throw new Error(`The "transformSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const formatMessagesSync: typeof types.formatMessagesSync = () => {
|
||||
throw new Error(`The "formatMessagesSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const analyzeMetafileSync: typeof types.analyzeMetafileSync = () => {
|
||||
throw new Error(`The "analyzeMetafileSync" API does not work in Deno`)
|
||||
}
|
||||
|
||||
export const stop = () => {
|
||||
if (stopService) stopService()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
interface Service {
|
||||
build: typeof types.build
|
||||
context: typeof types.context
|
||||
transform: typeof types.transform
|
||||
formatMessages: typeof types.formatMessages
|
||||
analyzeMetafile: typeof types.analyzeMetafile
|
||||
}
|
||||
|
||||
let initializePromise: Promise<Service> | undefined
|
||||
let stopService: (() => void) | undefined
|
||||
|
||||
let ensureServiceIsRunning = (): Promise<Service> => {
|
||||
return initializePromise || startRunningService('esbuild.wasm', undefined, true)
|
||||
}
|
||||
|
||||
export const initialize: typeof types.initialize = async (options) => {
|
||||
options = common.validateInitializeOptions(options || {})
|
||||
let wasmURL = options.wasmURL
|
||||
let wasmModule = options.wasmModule
|
||||
let useWorker = options.worker !== false
|
||||
if (initializePromise) throw new Error('Cannot call "initialize" more than once')
|
||||
initializePromise = startRunningService(wasmURL || 'esbuild.wasm', wasmModule, useWorker)
|
||||
initializePromise.catch(() => {
|
||||
// Let the caller try again if this fails
|
||||
initializePromise = void 0
|
||||
})
|
||||
await initializePromise
|
||||
}
|
||||
|
||||
const startRunningService = async (wasmURL: string | URL, wasmModule: WebAssembly.Module | undefined, useWorker: boolean): Promise<Service> => {
|
||||
let worker: {
|
||||
onmessage?: ((event: any) => void) | null | undefined
|
||||
postMessage: (data: Uint8Array | ArrayBuffer | WebAssembly.Module) => void
|
||||
terminate: () => void
|
||||
}
|
||||
|
||||
if (useWorker) {
|
||||
// Run esbuild off the main thread
|
||||
let blob = new Blob([`onmessage=${WEB_WORKER_SOURCE_CODE}(postMessage)`], { type: 'text/javascript' })
|
||||
worker = new Worker(URL.createObjectURL(blob), { type: 'module' })
|
||||
} else {
|
||||
// Run esbuild on the main thread
|
||||
let onmessage = WEB_WORKER_FUNCTION((data: Uint8Array) => worker.onmessage!({ data }))
|
||||
let go: Go | undefined
|
||||
worker = {
|
||||
onmessage: null,
|
||||
postMessage: data => setTimeout(() => go = onmessage({ data })),
|
||||
terminate() {
|
||||
if (go)
|
||||
for (let timeout of go._scheduledTimeouts.values())
|
||||
clearTimeout(timeout)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let firstMessageResolve: (value: void) => void
|
||||
let firstMessageReject: (error: any) => void
|
||||
|
||||
const firstMessagePromise = new Promise((resolve, reject) => {
|
||||
firstMessageResolve = resolve
|
||||
firstMessageReject = reject
|
||||
})
|
||||
|
||||
worker.onmessage = ({ data: error }) => {
|
||||
worker.onmessage = ({ data }) => readFromStdout(data)
|
||||
if (error) firstMessageReject(error)
|
||||
else firstMessageResolve()
|
||||
}
|
||||
|
||||
worker.postMessage(wasmModule || new URL(wasmURL, import.meta.url).toString())
|
||||
|
||||
let { readFromStdout, service } = common.createChannel({
|
||||
writeToStdin(bytes) {
|
||||
worker.postMessage(bytes)
|
||||
},
|
||||
isSync: false,
|
||||
hasFS: false,
|
||||
esbuild: ourselves,
|
||||
})
|
||||
|
||||
// This will throw if WebAssembly module instantiation fails
|
||||
await firstMessagePromise
|
||||
|
||||
stopService = () => {
|
||||
worker.terminate()
|
||||
initializePromise = undefined
|
||||
stopService = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
build: (options: types.BuildOptions) =>
|
||||
new Promise<types.BuildResult>((resolve, reject) =>
|
||||
service.buildOrContext({
|
||||
callName: 'build',
|
||||
refs: null,
|
||||
options,
|
||||
isTTY: false,
|
||||
defaultWD: '/',
|
||||
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildResult),
|
||||
})),
|
||||
|
||||
context: (options: types.BuildOptions) =>
|
||||
new Promise<types.BuildContext>((resolve, reject) =>
|
||||
service.buildOrContext({
|
||||
callName: 'context',
|
||||
refs: null,
|
||||
options,
|
||||
isTTY: false,
|
||||
defaultWD: '/',
|
||||
callback: (err, res) => err ? reject(err) : resolve(res as types.BuildContext),
|
||||
})),
|
||||
|
||||
transform: (input: string | Uint8Array, options?: types.TransformOptions) =>
|
||||
new Promise<types.TransformResult>((resolve, reject) =>
|
||||
service.transform({
|
||||
callName: 'transform',
|
||||
refs: null,
|
||||
input,
|
||||
options: options || {},
|
||||
isTTY: false,
|
||||
fs: {
|
||||
readFile(_, callback) { callback(new Error('Internal error'), null); },
|
||||
writeFile(_, callback) { callback(null); },
|
||||
},
|
||||
callback: (err, res) => err ? reject(err) : resolve(res!),
|
||||
})),
|
||||
|
||||
formatMessages: (messages, options) =>
|
||||
new Promise((resolve, reject) =>
|
||||
service.formatMessages({
|
||||
callName: 'formatMessages',
|
||||
refs: null,
|
||||
messages,
|
||||
options,
|
||||
callback: (err, res) => err ? reject(err) : resolve(res!),
|
||||
})),
|
||||
|
||||
analyzeMetafile: (metafile, options) =>
|
||||
new Promise((resolve, reject) =>
|
||||
service.analyzeMetafile({
|
||||
callName: 'analyzeMetafile',
|
||||
refs: null,
|
||||
metafile: typeof metafile === 'string' ? metafile : JSON.stringify(metafile),
|
||||
options,
|
||||
callback: (err, res) => err ? reject(err) : resolve(res!),
|
||||
})),
|
||||
}
|
||||
}
|
||||
+13
-5
@@ -222,10 +222,9 @@ export let analyzeMetafileSync: typeof types.analyzeMetafileSync = (metafile, op
|
||||
return result!
|
||||
}
|
||||
|
||||
export const stop = () => {
|
||||
if (stopService) stopService()
|
||||
export const stop = async () => {
|
||||
if (stopService) await stopService()
|
||||
if (workerThreadService) workerThreadService.stop()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
let initializeWasCalled = false
|
||||
@@ -251,7 +250,7 @@ interface Service {
|
||||
|
||||
let defaultWD = process.cwd()
|
||||
let longLivedService: Service | undefined
|
||||
let stopService: (() => void) | undefined
|
||||
let stopService: (() => Promise<void>) | undefined
|
||||
|
||||
let ensureServiceIsRunning = (): Service => {
|
||||
if (longLivedService) return longLivedService
|
||||
@@ -288,13 +287,22 @@ let ensureServiceIsRunning = (): Service => {
|
||||
stdout.on('end', afterClose)
|
||||
|
||||
stopService = () => {
|
||||
// Close all resources related to the subprocess.
|
||||
// Deno's implementation of node's API needs us to have a reference to the
|
||||
// child while we kill it. See: https://github.com/evanw/esbuild/pull/3701
|
||||
const promise = new Promise<void>(resolve => child.on('exit', () => {
|
||||
child.unref()
|
||||
resolve()
|
||||
}))
|
||||
child.ref()
|
||||
|
||||
// Close all resources related to the subprocess
|
||||
stdin.destroy()
|
||||
stdout.destroy()
|
||||
child.kill()
|
||||
initializeWasCalled = false
|
||||
longLivedService = undefined
|
||||
stopService = undefined
|
||||
return promise
|
||||
}
|
||||
|
||||
let refCount = 0
|
||||
|
||||
@@ -128,11 +128,13 @@ function pushLogFlags(flags: string[], options: CommonOptions, keys: OptionKeys,
|
||||
let color = getFlag(options, keys, 'color', mustBeBoolean)
|
||||
let logLevel = getFlag(options, keys, 'logLevel', mustBeString)
|
||||
let logLimit = getFlag(options, keys, 'logLimit', mustBeInteger)
|
||||
let logStyle = getFlag(options, keys, 'logStyle', mustBeString)
|
||||
|
||||
if (color !== void 0) flags.push(`--color=${color}`)
|
||||
else if (isTTY) flags.push(`--color=true`); // This is needed to fix "execFileSync" which buffers stderr
|
||||
flags.push(`--log-level=${logLevel || logLevelDefault}`)
|
||||
flags.push(`--log-limit=${logLimit || 0}`)
|
||||
if (logStyle) flags.push(`--log-style=${logStyle}`)
|
||||
}
|
||||
|
||||
function validateStringValue(value: unknown, what: string, key?: string): string {
|
||||
@@ -791,6 +793,7 @@ export function createChannel(streamIn: StreamIn): StreamOut {
|
||||
let kind = getFlag(options, keys, 'kind', mustBeString)
|
||||
let color = getFlag(options, keys, 'color', mustBeBoolean)
|
||||
let terminalWidth = getFlag(options, keys, 'terminalWidth', mustBeInteger)
|
||||
let logStyle = getFlag(options, keys, 'logStyle', mustBeString)
|
||||
checkForInvalidFlags(options, keys, `in ${callName}() call`)
|
||||
if (kind === void 0) throw new Error(`Missing "kind" in ${callName}() call`)
|
||||
if (kind !== 'error' && kind !== 'warning') throw new Error(`Expected "kind" to be "error" or "warning" in ${callName}() call`)
|
||||
@@ -801,6 +804,7 @@ export function createChannel(streamIn: StreamIn): StreamOut {
|
||||
}
|
||||
if (color !== void 0) request.color = color
|
||||
if (terminalWidth !== void 0) request.terminalWidth = terminalWidth
|
||||
if (logStyle !== void 0) request.logStyle = logStyle
|
||||
sendRequest<protocol.FormatMsgsRequest, protocol.FormatMsgsResponse>(refs, request, (error, response) => {
|
||||
if (error) return callback(new Error(error), null)
|
||||
callback(null, response!.messages)
|
||||
|
||||
@@ -135,6 +135,7 @@ export interface FormatMsgsRequest {
|
||||
isWarning: boolean
|
||||
color?: boolean
|
||||
terminalWidth?: number
|
||||
logStyle?: string
|
||||
}
|
||||
|
||||
export interface FormatMsgsResponse {
|
||||
|
||||
+6
-11
@@ -2,6 +2,7 @@ export type Platform = 'browser' | 'node' | 'neutral'
|
||||
export type Format = 'iife' | 'cjs' | 'esm'
|
||||
export type Loader = 'base64' | 'binary' | 'copy' | 'css' | 'dataurl' | 'default' | 'empty' | 'file' | 'js' | 'json' | 'jsx' | 'local-css' | 'text' | 'ts' | 'tsx'
|
||||
export type LogLevel = 'verbose' | 'debug' | 'info' | 'warning' | 'error' | 'silent'
|
||||
export type LogStyle = 'default' | 'clang' | 'visualstudio'
|
||||
export type Charset = 'ascii' | 'utf8'
|
||||
export type Drop = 'console' | 'debugger'
|
||||
export type AbsPaths = 'code' | 'log' | 'metafile'
|
||||
@@ -86,6 +87,8 @@ interface CommonOptions {
|
||||
logLimit?: number
|
||||
/** Documentation: https://esbuild.github.io/api/#log-override */
|
||||
logOverride?: Record<string, LogLevel>
|
||||
/** Documentation: https://esbuild.github.io/api/#log-style */
|
||||
logStyle?: LogStyle
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#tsconfig-raw */
|
||||
tsconfigRaw?: string | TsconfigRaw
|
||||
@@ -509,6 +512,7 @@ export interface FormatMessagesOptions {
|
||||
kind: 'error' | 'warning'
|
||||
color?: boolean
|
||||
terminalWidth?: number
|
||||
logStyle?: LogStyle
|
||||
}
|
||||
|
||||
export interface AnalyzeMetafileOptions {
|
||||
@@ -682,23 +686,14 @@ export let version: string
|
||||
// In node this happens automatically before the parent node process exits. So
|
||||
// you only need to call this if you know you will not make any more esbuild
|
||||
// API calls and you want to clean up resources.
|
||||
//
|
||||
// Unlike node, Deno lacks the necessary APIs to clean up child processes
|
||||
// automatically. You must manually call stop() in Deno when you're done
|
||||
// using esbuild or Deno will continue running forever.
|
||||
//
|
||||
// Another reason you might want to call this is if you are using esbuild from
|
||||
// within a Deno test. Deno fails tests that create a child process without
|
||||
// killing it before the test ends, so you have to call this function (and
|
||||
// await the returned promise) in every Deno test that uses esbuild.
|
||||
export declare function stop(): Promise<void>
|
||||
|
||||
// Note: These declarations exist to avoid type errors when you omit "dom" from
|
||||
// "lib" in your "tsconfig.json" file. TypeScript confusingly declares the
|
||||
// global "WebAssembly" type in "lib.dom.d.ts" even though it has nothing to do
|
||||
// with the browser DOM and is present in many non-browser JavaScript runtimes
|
||||
// (e.g. node and deno). Declaring it here allows esbuild's API to be used in
|
||||
// these scenarios.
|
||||
// (e.g. node). Declaring it here allows esbuild's API to be used in these
|
||||
// scenarios.
|
||||
//
|
||||
// There's an open issue about getting this problem corrected (although these
|
||||
// declarations will need to remain even if this is fixed for backward
|
||||
|
||||
@@ -8,7 +8,4 @@
|
||||
"node"
|
||||
],
|
||||
},
|
||||
"exclude": [
|
||||
"deno",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/aix-ppc64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The IBM AIX PowerPC 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/android-arm",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "A WebAssembly shim for esbuild on Android ARM.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/android-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Android ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/android-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "A WebAssembly shim for esbuild on Android x64.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/darwin-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The macOS ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/darwin-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The macOS 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/freebsd-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The FreeBSD ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/freebsd-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The FreeBSD 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-arm",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux ARM binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-ia32",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux 32-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-loong64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux LoongArch 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-mips64el",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux MIPS 64-bit Little Endian binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-ppc64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux PowerPC 64-bit Little Endian binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-riscv64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux RISC-V 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-s390x",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux IBM Z 64-bit Big Endian binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/linux-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Linux 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/netbsd-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The NetBSD ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/netbsd-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The NetBSD AMD64 binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/openbsd-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The OpenBSD ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/openbsd-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The OpenBSD 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/openharmony-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "A WebAssembly shim for esbuild on OpenHarmony ARM64.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/sunos-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The illumos 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/wasi-preview1",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The WASI (WebAssembly System Interface) preview 1 binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/win32-arm64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Windows ARM 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/win32-ia32",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Windows 32-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esbuild/win32-x64",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The Windows 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "esbuild-wasm",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "The cross-platform WebAssembly binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
+50
-50
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "esbuild",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"description": "An extremely fast JavaScript and CSS bundler and minifier.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -18,57 +18,57 @@
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.28.1",
|
||||
"@esbuild/android-arm": "0.28.1",
|
||||
"@esbuild/android-arm64": "0.28.1",
|
||||
"@esbuild/android-x64": "0.28.1",
|
||||
"@esbuild/darwin-arm64": "0.28.1",
|
||||
"@esbuild/darwin-x64": "0.28.1",
|
||||
"@esbuild/freebsd-arm64": "0.28.1",
|
||||
"@esbuild/freebsd-x64": "0.28.1",
|
||||
"@esbuild/linux-arm": "0.28.1",
|
||||
"@esbuild/linux-arm64": "0.28.1",
|
||||
"@esbuild/linux-ia32": "0.28.1",
|
||||
"@esbuild/linux-loong64": "0.28.1",
|
||||
"@esbuild/linux-mips64el": "0.28.1",
|
||||
"@esbuild/linux-ppc64": "0.28.1",
|
||||
"@esbuild/linux-riscv64": "0.28.1",
|
||||
"@esbuild/linux-s390x": "0.28.1",
|
||||
"@esbuild/linux-x64": "0.28.1",
|
||||
"@esbuild/netbsd-arm64": "0.28.1",
|
||||
"@esbuild/netbsd-x64": "0.28.1",
|
||||
"@esbuild/openbsd-arm64": "0.28.1",
|
||||
"@esbuild/openbsd-x64": "0.28.1",
|
||||
"@esbuild/openharmony-arm64": "0.28.1",
|
||||
"@esbuild/sunos-x64": "0.28.1",
|
||||
"@esbuild/win32-arm64": "0.28.1",
|
||||
"@esbuild/win32-ia32": "0.28.1",
|
||||
"@esbuild/win32-x64": "0.28.1"
|
||||
"@esbuild/aix-ppc64": "0.28.2",
|
||||
"@esbuild/android-arm": "0.28.2",
|
||||
"@esbuild/android-arm64": "0.28.2",
|
||||
"@esbuild/android-x64": "0.28.2",
|
||||
"@esbuild/darwin-arm64": "0.28.2",
|
||||
"@esbuild/darwin-x64": "0.28.2",
|
||||
"@esbuild/freebsd-arm64": "0.28.2",
|
||||
"@esbuild/freebsd-x64": "0.28.2",
|
||||
"@esbuild/linux-arm": "0.28.2",
|
||||
"@esbuild/linux-arm64": "0.28.2",
|
||||
"@esbuild/linux-ia32": "0.28.2",
|
||||
"@esbuild/linux-loong64": "0.28.2",
|
||||
"@esbuild/linux-mips64el": "0.28.2",
|
||||
"@esbuild/linux-ppc64": "0.28.2",
|
||||
"@esbuild/linux-riscv64": "0.28.2",
|
||||
"@esbuild/linux-s390x": "0.28.2",
|
||||
"@esbuild/linux-x64": "0.28.2",
|
||||
"@esbuild/netbsd-arm64": "0.28.2",
|
||||
"@esbuild/netbsd-x64": "0.28.2",
|
||||
"@esbuild/openbsd-arm64": "0.28.2",
|
||||
"@esbuild/openbsd-x64": "0.28.2",
|
||||
"@esbuild/openharmony-arm64": "0.28.2",
|
||||
"@esbuild/sunos-x64": "0.28.2",
|
||||
"@esbuild/win32-arm64": "0.28.2",
|
||||
"@esbuild/win32-ia32": "0.28.2",
|
||||
"@esbuild/win32-x64": "0.28.2"
|
||||
},
|
||||
"license": "MIT",
|
||||
"esbuild.binaryHashes": {
|
||||
"@esbuild/aix-ppc64/bin/esbuild": "0dd860154344f94b7a9367e64ffc2bc5b89a4313b4a399e5127cdc4ff5000303",
|
||||
"@esbuild/android-arm64/bin/esbuild": "087671da02d323e7915fafc13567c0929192633ed544bd8adee9bc020fddf5b4",
|
||||
"@esbuild/darwin-arm64/bin/esbuild": "e2dc9a52440a2a34f09434a2f4843cb1e30f84e40dcf238976ec61ef8cd7f36a",
|
||||
"@esbuild/darwin-x64/bin/esbuild": "dd53ccf32f9b5b3ab30d41388ef1fc8f81c44ca57ee7a32a7364a1753308d009",
|
||||
"@esbuild/freebsd-arm64/bin/esbuild": "5fef5a5ef24d9660d7711b756be5ddeba1fb3ca8dd62d703682ebb9fab3b68b0",
|
||||
"@esbuild/freebsd-x64/bin/esbuild": "4427a8cbfa0661943ccfef2606a65b35f3411ac73e447e86f7732aace61e4609",
|
||||
"@esbuild/linux-arm/bin/esbuild": "a2d93f7a8b1c9f91d47beec987888b4765c890501b6274f4bdff4061a5e0d9fd",
|
||||
"@esbuild/linux-arm64/bin/esbuild": "51e829ba36f36be6d9aea6e329ddc4f9350302339b16aaca96a3cb97f64a8ebb",
|
||||
"@esbuild/linux-ia32/bin/esbuild": "9cd7515a75d6f96b0aa055861cf987498315428088cd6d18de5082f904ae5794",
|
||||
"@esbuild/linux-loong64/bin/esbuild": "73fa76af9b13f7a27fdb20673b8c2af927fe1511d14e51edeb4b7b71f7f03d0e",
|
||||
"@esbuild/linux-mips64el/bin/esbuild": "d734787abc3c99d95242805047fe32da85a5822f04bf33d766a966bf5fd85488",
|
||||
"@esbuild/linux-ppc64/bin/esbuild": "90709144617c2adf8f6c14937f03b114c259de0b9309666e3a9d89a1fb266d04",
|
||||
"@esbuild/linux-riscv64/bin/esbuild": "86df35a46bedde8b756edb6565a1df7b5a832ad74dd0e179608cbf6509a2582a",
|
||||
"@esbuild/linux-s390x/bin/esbuild": "f8cad0db35132e5ac3065c4345c36e7ffe18fdc98417a61bcf5a95a6c694e57c",
|
||||
"@esbuild/linux-x64/bin/esbuild": "0c6588b092a2c291a72bab90659f3c9e0e25e0fe59c9ac12b4dae4d945e5548c",
|
||||
"@esbuild/netbsd-arm64/bin/esbuild": "490cfdac2d96d5608d91cf55bdf4052bbde7013dafe87ec7adc7784778ad4d4c",
|
||||
"@esbuild/netbsd-x64/bin/esbuild": "c61e0e4c553a4986fd0e9d63a0425e93be831ff7b13bfb8a564368fb7590f85d",
|
||||
"@esbuild/openbsd-arm64/bin/esbuild": "f9d814698e26c09c3f90d4c103cbdf3832c9f2c841d64a93889d63d23da9b8cf",
|
||||
"@esbuild/openbsd-x64/bin/esbuild": "2e30dce11f7236aa362c1c57212cc696f51472169aa417f30e9b641f3b7dcade",
|
||||
"@esbuild/sunos-x64/bin/esbuild": "770e0d2bc930b1a1d19d3f43243ff8f393efddb599e7f5843160feacd253267d",
|
||||
"@esbuild/win32-arm64/esbuild.exe": "bfb8798ab678f1ce4a723739f4a3eabab3244d7a04eeb12be2eb9f58095c13ef",
|
||||
"@esbuild/win32-ia32/esbuild.exe": "8fa99b6e0945830fce8d7e208fdb21763aa4aea875751f4a0eec7f6e262af1dd",
|
||||
"@esbuild/win32-x64/esbuild.exe": "ec02ee9b14ab332416fedd10614dfb80eed5304d94f67745067c011934a8c3c3"
|
||||
"@esbuild/aix-ppc64/bin/esbuild": "6de148b932fc5d3b28a97f9f0bc4de8e2b47db478b8ad679e0eaa6b96e00420d",
|
||||
"@esbuild/android-arm64/bin/esbuild": "84efbbd2cbf21bbfec2aaf304bee79cb2a4a05fe2ea7b861a87f7b8f213ac27d",
|
||||
"@esbuild/darwin-arm64/bin/esbuild": "10b6243df618d374bb2d5c9cfbe7052e1405f6aa4e53a6164f11a91b9f2e1384",
|
||||
"@esbuild/darwin-x64/bin/esbuild": "7fe5c9c905fff0a05d92db98929434ab4d3b6dd92d7a7688922db74380c75df3",
|
||||
"@esbuild/freebsd-arm64/bin/esbuild": "6aba108a68b93064e491bfb1ecfa28843b4d4e504dff0627f53f68f6bb015c01",
|
||||
"@esbuild/freebsd-x64/bin/esbuild": "8a237e222bee0e50556e7b00d83ec2ff43721bf72f19811a5f0ee56f1d40177c",
|
||||
"@esbuild/linux-arm/bin/esbuild": "d88f751b22095b2f3b42c8c2e8a3f36fabc1705438c5d6e78366b681f8b4e7a5",
|
||||
"@esbuild/linux-arm64/bin/esbuild": "90bd269553d258e80b19e3ccab4f98f0831f87442a2f056510ce24fd1b425fc2",
|
||||
"@esbuild/linux-ia32/bin/esbuild": "b1e2d87d79f9a2baa0d3043cdf0dc6716f9f2eea66c58f47588641fd6019f36f",
|
||||
"@esbuild/linux-loong64/bin/esbuild": "55a66f9854d6798f36b2b5471db74f2e552481887ea14d8bda1634a512383a43",
|
||||
"@esbuild/linux-mips64el/bin/esbuild": "6bf7921a3af4a801051e056d85c360fe24bb446f488ef3da25f0b8e5cf973db1",
|
||||
"@esbuild/linux-ppc64/bin/esbuild": "c30df60d09bb0ba4853d7b06e244ce966890d61ce60cadd9b22335f6379c316a",
|
||||
"@esbuild/linux-riscv64/bin/esbuild": "36bdcdc8230eef385838cdfeb0d9a1565cbe4b9ace30097a511d77e894de36a1",
|
||||
"@esbuild/linux-s390x/bin/esbuild": "bbbc1f92a76a5ca6e2976065222e42b02797363a1f119490dda315f74047741f",
|
||||
"@esbuild/linux-x64/bin/esbuild": "e1698a3d5c6c0798fee4fd3b5cc816651f460c63d390a7a26ea4beb0b1884100",
|
||||
"@esbuild/netbsd-arm64/bin/esbuild": "62548519d7ae875d060aa8c94bfb8ed38ef191671a5173d9f626bdb6b7516970",
|
||||
"@esbuild/netbsd-x64/bin/esbuild": "f9d0e59d2b404559e3a0c958164d96960ebf0e04583b878b1d64d8f1c3698fcc",
|
||||
"@esbuild/openbsd-arm64/bin/esbuild": "e10c45a0b62456632da41df058d7626b2e7cfe710780a2dc7f9f84681753f3aa",
|
||||
"@esbuild/openbsd-x64/bin/esbuild": "720bbe607736716f2ecd37962436c5589d801966e868a2ae4e0e7e62d1e4e887",
|
||||
"@esbuild/sunos-x64/bin/esbuild": "01a3bd9183631ca749f34d15601ea21ba6834614184c5c859f99d54bd683203d",
|
||||
"@esbuild/win32-arm64/esbuild.exe": "530fb3933af14eefe85dfd06502f826e6ef60fdf4e75228ea67c696db312ecb1",
|
||||
"@esbuild/win32-ia32/esbuild.exe": "b2e62af5c17e00939583be0a0762fdc469f352869a617edc91371b00454c76f0",
|
||||
"@esbuild/win32-x64/esbuild.exe": "c7bee37877d0aa6a046e52783fa0a2cf1a9ce5579d68bb3083bda99d4bff18ef"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ const (
|
||||
ES2023
|
||||
ES2024
|
||||
ES2025
|
||||
ES2026
|
||||
)
|
||||
|
||||
type Loader uint16
|
||||
@@ -236,6 +237,14 @@ const (
|
||||
LogLevelError
|
||||
)
|
||||
|
||||
type LogStyle uint8
|
||||
|
||||
const (
|
||||
LogStyleDefault LogStyle = iota
|
||||
LogStyleClang
|
||||
LogStyleVisualStudio
|
||||
)
|
||||
|
||||
type Charset uint8
|
||||
|
||||
const (
|
||||
@@ -280,6 +289,7 @@ const (
|
||||
type BuildOptions struct {
|
||||
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
|
||||
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
|
||||
LogStyle LogStyle // Documentation: https://esbuild.github.io/api/#log-style
|
||||
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
|
||||
LogOverride map[string]LogLevel // Documentation: https://esbuild.github.io/api/#log-override
|
||||
AbsPaths AbsPaths // Documentation: https://esbuild.github.io/api/#abs-path
|
||||
@@ -412,6 +422,7 @@ func Build(options BuildOptions) BuildResult {
|
||||
type TransformOptions struct {
|
||||
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
|
||||
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
|
||||
LogStyle LogStyle // Documentation: https://esbuild.github.io/api/#log-style
|
||||
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
|
||||
LogOverride map[string]LogLevel // Documentation: https://esbuild.github.io/api/#log-override
|
||||
AbsPaths AbsPaths // Documentation: https://esbuild.github.io/api/#abs-path
|
||||
@@ -717,6 +728,7 @@ type FormatMessagesOptions struct {
|
||||
TerminalWidth int
|
||||
Kind MessageKind
|
||||
Color bool
|
||||
LogStyle LogStyle
|
||||
}
|
||||
|
||||
func FormatMessages(msgs []Message, opts FormatMessagesOptions) []string {
|
||||
|
||||
+50
-24
@@ -205,6 +205,19 @@ func validateLogLevel(value LogLevel) logger.LogLevel {
|
||||
}
|
||||
}
|
||||
|
||||
func validateLogStyle(value LogStyle) logger.LogStyle {
|
||||
switch value {
|
||||
case LogStyleDefault:
|
||||
return logger.StyleDefault
|
||||
case LogStyleClang:
|
||||
return logger.StyleClang
|
||||
case LogStyleVisualStudio:
|
||||
return logger.StyleVisualStudio
|
||||
default:
|
||||
panic("Invalid log style")
|
||||
}
|
||||
}
|
||||
|
||||
func validateASCIIOnly(value Charset) bool {
|
||||
switch value {
|
||||
case CharsetDefault, CharsetASCII:
|
||||
@@ -330,6 +343,8 @@ func validateFeatures(log logger.Log, target Target, engines []Engine) (compat.J
|
||||
constraints[compat.ES] = compat.Semver{Parts: []int{2024}}
|
||||
case ES2025:
|
||||
constraints[compat.ES] = compat.Semver{Parts: []int{2025}}
|
||||
case ES2026:
|
||||
constraints[compat.ES] = compat.Semver{Parts: []int{2026}}
|
||||
case ESNext, DefaultTarget:
|
||||
default:
|
||||
panic("Invalid target")
|
||||
@@ -345,10 +360,15 @@ func validateFeatures(log logger.Log, target Target, engines []Engine) (compat.J
|
||||
parts = append(parts, patch)
|
||||
}
|
||||
}
|
||||
constraints[convertEngineName(engine.Name)] = compat.Semver{
|
||||
name := convertEngineName(engine.Name)
|
||||
new := compat.Semver{
|
||||
Parts: parts,
|
||||
PreRelease: match[4],
|
||||
}
|
||||
if old, ok := constraints[name]; ok && compat.CompareSemver(old, new) < 0 {
|
||||
continue
|
||||
}
|
||||
constraints[name] = new
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -884,6 +904,7 @@ func contextImpl(buildOpts BuildOptions) (*internalContext, []Message) {
|
||||
MessageLimit: buildOpts.LogLimit,
|
||||
Color: validateColor(buildOpts.Color),
|
||||
LogLevel: validateLogLevel(buildOpts.LogLevel),
|
||||
LogStyle: validateLogStyle(buildOpts.LogStyle),
|
||||
PathStyle: extractPathStyle(buildOpts.AbsPaths, LogAbsPath),
|
||||
Overrides: validateLogOverrides(buildOpts.LogOverride),
|
||||
}
|
||||
@@ -1494,10 +1515,7 @@ func rebuildImpl(args rebuildArgs, oldHashes map[string]string) (rebuildState, m
|
||||
// Scan over the bundle
|
||||
bundle := bundler.ScanBundle(config.BuildCall, log, realFS, args.caches, args.entryPoints, args.options, timer)
|
||||
watchData = realFS.WatchData()
|
||||
|
||||
// The new build summary remains the same as the old one when there are
|
||||
// errors. A failed build shouldn't erase the previous successful build.
|
||||
newHashes := oldHashes
|
||||
newHashes := make(map[string]string)
|
||||
|
||||
// Stop now if there were errors
|
||||
var results []graph.OutputFile
|
||||
@@ -1515,27 +1533,26 @@ func rebuildImpl(args rebuildArgs, oldHashes map[string]string) (rebuildState, m
|
||||
// Stop now if there were errors
|
||||
if !log.HasErrors() {
|
||||
result.Metafile = metafile
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the results to return
|
||||
var hashBytes [8]byte
|
||||
result.OutputFiles = make([]OutputFile, len(results))
|
||||
newHashes = make(map[string]string)
|
||||
for i, item := range results {
|
||||
if args.options.WriteToStdout {
|
||||
item.AbsPath = "<stdout>"
|
||||
// Populate the results to return
|
||||
var hashBytes [8]byte
|
||||
result.OutputFiles = make([]OutputFile, len(results))
|
||||
for i, item := range results {
|
||||
if args.options.WriteToStdout {
|
||||
item.AbsPath = "<stdout>"
|
||||
}
|
||||
hasher := xxhash.New()
|
||||
hasher.Write(item.Contents)
|
||||
binary.LittleEndian.PutUint64(hashBytes[:], hasher.Sum64())
|
||||
hash := base64.RawStdEncoding.EncodeToString(hashBytes[:])
|
||||
result.OutputFiles[i] = OutputFile{
|
||||
Path: item.AbsPath,
|
||||
Contents: item.Contents,
|
||||
Hash: hash,
|
||||
}
|
||||
newHashes[item.AbsPath] = hash
|
||||
}
|
||||
}
|
||||
hasher := xxhash.New()
|
||||
hasher.Write(item.Contents)
|
||||
binary.LittleEndian.PutUint64(hashBytes[:], hasher.Sum64())
|
||||
hash := base64.RawStdEncoding.EncodeToString(hashBytes[:])
|
||||
result.OutputFiles[i] = OutputFile{
|
||||
Path: item.AbsPath,
|
||||
Contents: item.Contents,
|
||||
Hash: hash,
|
||||
}
|
||||
newHashes[item.AbsPath] = hash
|
||||
}
|
||||
|
||||
// Write output files before "OnEnd" callbacks run so they can expect
|
||||
@@ -1563,12 +1580,19 @@ func rebuildImpl(args rebuildArgs, oldHashes map[string]string) (rebuildState, m
|
||||
}
|
||||
}
|
||||
|
||||
// Only write output files if there were no errors. See this issue
|
||||
// for why: https://github.com/evanw/esbuild/issues/3643
|
||||
shouldWriteFiles := !log.HasErrors()
|
||||
|
||||
// Process all file operations in parallel
|
||||
waitGroup := sync.WaitGroup{}
|
||||
waitGroup.Add(len(results) + len(toDelete))
|
||||
for _, result := range results {
|
||||
go func(result graph.OutputFile) {
|
||||
defer waitGroup.Done()
|
||||
if !shouldWriteFiles {
|
||||
return
|
||||
}
|
||||
fs.BeforeFileOpen()
|
||||
defer fs.AfterFileClose()
|
||||
if oldHash, ok := oldHashes[result.AbsPath]; ok && oldHash == newHashes[result.AbsPath] {
|
||||
@@ -1690,6 +1714,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult
|
||||
MessageLimit: transformOpts.LogLimit,
|
||||
Color: validateColor(transformOpts.Color),
|
||||
LogLevel: validateLogLevel(transformOpts.LogLevel),
|
||||
LogStyle: validateLogStyle(transformOpts.LogStyle),
|
||||
PathStyle: extractPathStyle(transformOpts.AbsPaths, LogAbsPath),
|
||||
Overrides: validateLogOverrides(transformOpts.LogOverride),
|
||||
})
|
||||
@@ -2199,6 +2224,7 @@ func formatMsgsImpl(msgs []Message, opts FormatMessagesOptions) []string {
|
||||
strings[i] = msg.String(
|
||||
logger.OutputOptions{
|
||||
IncludeSource: true,
|
||||
LogStyle: validateLogStyle(opts.LogStyle),
|
||||
},
|
||||
logger.TerminalInfo{
|
||||
UseColorEscapes: opts.Color,
|
||||
|
||||
+18
-1
@@ -153,7 +153,7 @@ func TestFormatMessages(t *testing.T) {
|
||||
}},
|
||||
`✘ [ERROR] This is a test
|
||||
|
||||
some file.js:100:59:
|
||||
some file.js:100:200:
|
||||
100 │ ...defghi ABCDEFGHI
|
||||
╵ ^
|
||||
|
||||
@@ -319,3 +319,20 @@ func TestFormatMessages(t *testing.T) {
|
||||
`,
|
||||
)
|
||||
}
|
||||
|
||||
func TestEngines(t *testing.T) {
|
||||
checkChromeVersions := func(a string, b string) string {
|
||||
result := api.Transform("a ?? b", api.TransformOptions{
|
||||
Engines: []api.Engine{
|
||||
{Name: api.EngineChrome, Version: a},
|
||||
{Name: api.EngineChrome, Version: b},
|
||||
},
|
||||
})
|
||||
return string(result.Code)
|
||||
}
|
||||
|
||||
test.AssertEqualWithDiff(t, checkChromeVersions("99", "99"), "a ?? b;\n")
|
||||
test.AssertEqualWithDiff(t, checkChromeVersions("9", "9"), "a != null ? a : b;\n")
|
||||
test.AssertEqualWithDiff(t, checkChromeVersions("9", "99"), "a != null ? a : b;\n")
|
||||
test.AssertEqualWithDiff(t, checkChromeVersions("99", "9"), "a != null ? a : b;\n")
|
||||
}
|
||||
|
||||
+37
-14
@@ -227,18 +227,19 @@ func (h *apiHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
// Check for a file in the "servedir" directory
|
||||
if h.servedir != "" && kind != fs.FileEntry {
|
||||
absPath := h.fs.Join(h.servedir, queryPath)
|
||||
if symlink, ok := h.fs.EvalSymlinks(absPath); ok {
|
||||
absPath = symlink
|
||||
}
|
||||
if absDir := h.fs.Dir(absPath); absDir != absPath {
|
||||
if entries, err, _ := h.fs.ReadDirectory(absDir); err == nil {
|
||||
if entry, _ := entries.Get(h.fs.Base(absPath)); entry != nil && entry.Kind(h.fs) == fs.FileEntry {
|
||||
if h.keyfileToLower != "" || h.certfileToLower != "" {
|
||||
if toLower := strings.ToLower(absPath); toLower == h.keyfileToLower || toLower == h.certfileToLower {
|
||||
// Don't serve the HTTPS key or certificate. This uses a case-
|
||||
// insensitive check because some file systems are case-sensitive.
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusForbidden)
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
maybeWriteResponseBody([]byte("403 - Forbidden"))
|
||||
return
|
||||
}
|
||||
if strings.EqualFold(absPath, h.keyfileToLower) || strings.EqualFold(absPath, h.certfileToLower) {
|
||||
// Don't serve the HTTPS key or certificate. This uses a case-
|
||||
// insensitive check because some file systems are case-sensitive.
|
||||
go h.notifyRequest(time.Since(start), req, http.StatusForbidden)
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
maybeWriteResponseBody([]byte("403 - Forbidden"))
|
||||
return
|
||||
}
|
||||
if contents, err, _ := h.fs.OpenFile(absPath); err == nil {
|
||||
defer contents.Close()
|
||||
@@ -346,12 +347,28 @@ func (h *apiHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// If we get here, the request was successful
|
||||
if contentType := helpers.MimeTypeByExtension(h.fs.Ext(file.absPath)); contentType != "" {
|
||||
res.Header().Set("Content-Type", contentType)
|
||||
} else {
|
||||
res.Header().Set("Content-Type", "application/octet-stream")
|
||||
// Try to detect the MIME type
|
||||
contentType := helpers.MimeTypeByExtension(h.fs.Ext(file.absPath))
|
||||
if contentType == "" {
|
||||
if begin == 0 {
|
||||
contentType = http.DetectContentType(fileBytes)
|
||||
} else {
|
||||
// Read the file header for MIME type detection of HTTP range requests
|
||||
limit := 512
|
||||
if limit > fileContentsLen {
|
||||
limit = fileContentsLen
|
||||
}
|
||||
if headerBytes, err := file.contents.Read(0, limit); err == nil {
|
||||
contentType = http.DetectContentType(headerBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// If we get here, the request was successful
|
||||
res.Header().Set("Content-Type", contentType)
|
||||
if isRange {
|
||||
res.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", begin, end-1, fileContentsLen))
|
||||
}
|
||||
@@ -888,6 +905,12 @@ func (ctx *internalContext) Serve(serveOptions ServeOptions) (ServeResult, error
|
||||
if isHTTPS {
|
||||
serveOptions.Keyfile, _ = ctx.realFS.Abs(serveOptions.Keyfile)
|
||||
serveOptions.Certfile, _ = ctx.realFS.Abs(serveOptions.Certfile)
|
||||
if symlink, ok := ctx.realFS.EvalSymlinks(serveOptions.Keyfile); ok {
|
||||
serveOptions.Keyfile = symlink
|
||||
}
|
||||
if symlink, ok := ctx.realFS.EvalSymlinks(serveOptions.Certfile); ok {
|
||||
serveOptions.Certfile = symlink
|
||||
}
|
||||
}
|
||||
|
||||
var shouldStop int32
|
||||
|
||||
@@ -836,6 +836,19 @@ func parseOptionsImpl(
|
||||
transformOpts.LogLevel = logLevel
|
||||
}
|
||||
|
||||
// Make sure this stays in sync with "PrintErrorToStderr"
|
||||
case strings.HasPrefix(arg, "--log-style="):
|
||||
value := arg[len("--log-style="):]
|
||||
logStyle, err := parseLogStyle(value, arg)
|
||||
if err != nil {
|
||||
return parseOptionsExtras{}, err
|
||||
}
|
||||
if buildOpts != nil {
|
||||
buildOpts.LogStyle = logStyle
|
||||
} else {
|
||||
transformOpts.LogStyle = logStyle
|
||||
}
|
||||
|
||||
case strings.HasPrefix(arg, "'--"):
|
||||
return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
|
||||
fmt.Sprintf("Unexpected single quote character before flag: %s", arg),
|
||||
@@ -1025,6 +1038,7 @@ func parseTargets(targets []string, arg string) (target api.Target, engines []ap
|
||||
"es2023": api.ES2023,
|
||||
"es2024": api.ES2024,
|
||||
"es2025": api.ES2025,
|
||||
"es2026": api.ES2026,
|
||||
}
|
||||
|
||||
outer:
|
||||
@@ -1562,3 +1576,19 @@ func parseLogLevel(value string, arg string) (api.LogLevel, *cli_helpers.ErrorWi
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func parseLogStyle(value string, arg string) (api.LogStyle, *cli_helpers.ErrorWithNote) {
|
||||
switch value {
|
||||
case "default":
|
||||
return api.LogStyleDefault, nil
|
||||
case "clang":
|
||||
return api.LogStyleClang, nil
|
||||
case "visualstudio":
|
||||
return api.LogStyleVisualStudio, nil
|
||||
default:
|
||||
return api.LogStyleDefault, cli_helpers.MakeErrorWithNote(
|
||||
fmt.Sprintf("Invalid value %q in %q", value, arg),
|
||||
"Valid values are \"default\", \"clang\", or \"visualstudio\".",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +302,34 @@
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/4497
|
||||
async cssNestingIssue4497() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.a {
|
||||
color: red;
|
||||
|
||||
.b:not(.a .b .b) {
|
||||
color: green;
|
||||
|
||||
& .b:not(& .b .b) {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="a">
|
||||
<span>1</span>
|
||||
<div class="b">
|
||||
<span>2</span>
|
||||
<div class="b"><span>3</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
async function runTest(test, fn) {
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
// To run this, you must first build the Deno package with "make platform-deno"
|
||||
import * as esbuildNative from '../deno/mod.js'
|
||||
import * as esbuildWASM from '../deno/wasm.js'
|
||||
import * as path from 'https://deno.land/[email protected]/path/mod.ts'
|
||||
import * as asserts from 'https://deno.land/[email protected]/testing/asserts.ts'
|
||||
|
||||
const __dirname = path.dirname(path.fromFileUrl(import.meta.url))
|
||||
const rootTestDir = path.join(__dirname, '.deno-tests')
|
||||
const wasmModule = await WebAssembly.compile(await Deno.readFile(path.join(__dirname, '..', 'deno', 'esbuild.wasm')))
|
||||
|
||||
try {
|
||||
Deno.removeSync(rootTestDir, { recursive: true })
|
||||
} catch {
|
||||
}
|
||||
Deno.mkdirSync(rootTestDir, { recursive: true })
|
||||
|
||||
function test(name, backends, fn) {
|
||||
// Note: Do not try to add a timeout for the tests below. It turns out that
|
||||
// calling "setTimeout" from within "Deno.test" changes how Deno waits for
|
||||
// promises in a way that masks problems that would otherwise occur. We want
|
||||
// test coverage for the way that people will likely be using these API calls.
|
||||
//
|
||||
// Specifically tests that Deno would otherwise have failed with "error:
|
||||
// Promise resolution is still pending but the event loop has already
|
||||
// resolved" were being allowed to pass instead. See this issue for more
|
||||
// information: https://github.com/evanw/esbuild/issues/3682
|
||||
|
||||
for (const backend of backends) {
|
||||
switch (backend) {
|
||||
case 'native':
|
||||
Deno.test(name + '-native', async () => {
|
||||
let testDir = path.join(rootTestDir, name + '-native')
|
||||
await Deno.mkdir(testDir, { recursive: true })
|
||||
try {
|
||||
await fn({ esbuild: esbuildNative, testDir })
|
||||
await Deno.remove(testDir, { recursive: true }).catch(() => null)
|
||||
} finally {
|
||||
await esbuildNative.stop()
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
case 'wasm-main':
|
||||
Deno.test(name + '-wasm-main', async () => {
|
||||
let testDir = path.join(rootTestDir, name + '-wasm-main')
|
||||
await esbuildWASM.initialize({ wasmModule, worker: false })
|
||||
await Deno.mkdir(testDir, { recursive: true })
|
||||
try {
|
||||
await fn({ esbuild: esbuildWASM, testDir })
|
||||
await Deno.remove(testDir, { recursive: true }).catch(() => null)
|
||||
} finally {
|
||||
await esbuildWASM.stop()
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
case 'wasm-worker':
|
||||
Deno.test(name + '-wasm-worker', async () => {
|
||||
let testDir = path.join(rootTestDir, name + '-wasm-worker')
|
||||
await esbuildWASM.initialize({ wasmModule, worker: true })
|
||||
await Deno.mkdir(testDir, { recursive: true })
|
||||
try {
|
||||
await fn({ esbuild: esbuildWASM, testDir })
|
||||
await Deno.remove(testDir, { recursive: true }).catch(() => null)
|
||||
} finally {
|
||||
await esbuildWASM.stop()
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener("unload", (e) => {
|
||||
try {
|
||||
Deno.removeSync(rootTestDir, { recursive: true })
|
||||
} catch {
|
||||
// root test dir possibly already removed, so ignore
|
||||
}
|
||||
})
|
||||
|
||||
// This test doesn't run in WebAssembly because it requires file system access
|
||||
test("basicBuild", ['native'], async ({ esbuild, testDir }) => {
|
||||
const input = path.join(testDir, 'in.ts')
|
||||
const dep = path.join(testDir, 'dep.ts')
|
||||
const output = path.join(testDir, 'out.ts')
|
||||
await Deno.writeTextFile(input, 'import dep from "./dep.ts"; export default dep === 123')
|
||||
await Deno.writeTextFile(dep, 'export default 123')
|
||||
await esbuild.build({
|
||||
entryPoints: [input],
|
||||
bundle: true,
|
||||
outfile: output,
|
||||
format: 'esm',
|
||||
})
|
||||
const result = await import(path.toFileUrl(output))
|
||||
asserts.assertStrictEquals(result.default, true)
|
||||
})
|
||||
|
||||
test("basicContext", ['native'], async ({ esbuild, testDir }) => {
|
||||
const input = path.join(testDir, 'in.ts')
|
||||
const dep = path.join(testDir, 'dep.ts')
|
||||
const output = path.join(testDir, 'out.ts')
|
||||
await Deno.writeTextFile(input, 'import dep from "./dep.ts"; export default dep === 123')
|
||||
await Deno.writeTextFile(dep, 'export default 123')
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: ['in.ts'],
|
||||
bundle: true,
|
||||
outfile: output,
|
||||
format: 'esm',
|
||||
absWorkingDir: testDir,
|
||||
})
|
||||
const { errors, warnings } = await ctx.rebuild()
|
||||
asserts.assertStrictEquals(errors.length, 0)
|
||||
asserts.assertStrictEquals(warnings.length, 0)
|
||||
await ctx.dispose()
|
||||
const result = await import(path.toFileUrl(output))
|
||||
asserts.assertStrictEquals(result.default, true)
|
||||
})
|
||||
|
||||
test("basicPlugin", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
|
||||
const build = await esbuild.build({
|
||||
entryPoints: ['<entry>'],
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
write: false,
|
||||
plugins: [{
|
||||
name: 'plug',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^<.*>$/ }, args => ({ path: args.path, namespace: '<>' }))
|
||||
build.onLoad({ filter: /^<entry>$/ }, () => ({ contents: `import dep from "<dep>"; export default dep === 123` }))
|
||||
build.onLoad({ filter: /^<dep>$/ }, () => ({ contents: `export default 123` }))
|
||||
},
|
||||
}],
|
||||
})
|
||||
const result = await import('data:application/javascript;base64,' + btoa(build.outputFiles[0].text))
|
||||
asserts.assertStrictEquals(result.default, true)
|
||||
})
|
||||
|
||||
test("basicTransform", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
|
||||
const ts = 'let x: number = 1+2'
|
||||
const result = await esbuild.transform(ts, { loader: 'ts' })
|
||||
asserts.assertStrictEquals(result.code, 'let x = 1 + 2;\n')
|
||||
})
|
||||
|
||||
// This test doesn't run in WebAssembly because of a stack overflow
|
||||
test("largeTransform", ['native'], async ({ esbuild }) => {
|
||||
// This should be large enough to be bigger than Deno's write buffer
|
||||
let x = '0'
|
||||
for (let i = 0; i < 1000; i++) x += '+' + i
|
||||
x += ','
|
||||
let y = 'return['
|
||||
for (let i = 0; i < 1000; i++) y += x
|
||||
y += ']'
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: y,
|
||||
},
|
||||
write: false,
|
||||
minifyWhitespace: true,
|
||||
})
|
||||
asserts.assertStrictEquals(result.outputFiles[0].text, y.slice(0, -2) + '];\n')
|
||||
})
|
||||
|
||||
test("analyzeMetafile", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
|
||||
const result = await esbuild.analyzeMetafile({
|
||||
outputs: {
|
||||
'out.js': {
|
||||
bytes: 4096,
|
||||
inputs: {
|
||||
'in.js': {
|
||||
bytesInOutput: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
asserts.assertStrictEquals(result, `
|
||||
out.js 4.0kb 100.0%
|
||||
└ in.js 1.0kb 25.0%
|
||||
`)
|
||||
})
|
||||
@@ -1519,6 +1519,45 @@ for (const target of ['--target=es5', '--target=es6', '--target=es2020']) {
|
||||
)
|
||||
}
|
||||
|
||||
// Check lowering of logical assignment and nullish coalescing operators
|
||||
// See: https://github.com/evanw/esbuild/issues/4508
|
||||
for (const minify of [[], ['--minify']]) {
|
||||
for (const target of ['es6', 'esnext']) {
|
||||
tests.push(
|
||||
test(['in.js', '--outfile=node.js', '--format=esm', '--target=' + target].concat(minify), {
|
||||
'in.js': `
|
||||
function foo() {
|
||||
let x
|
||||
x ||= {}
|
||||
return 123
|
||||
}
|
||||
if (foo() !== 123) throw 'fail'
|
||||
`,
|
||||
}),
|
||||
test(['in.js', '--outfile=node.js', '--format=esm', '--target=' + target].concat(minify), {
|
||||
'in.js': `
|
||||
function foo() {
|
||||
let x
|
||||
x &&= {}
|
||||
return 123
|
||||
}
|
||||
if (foo() !== 123) throw 'fail'
|
||||
`,
|
||||
}),
|
||||
test(['in.js', '--outfile=node.js', '--format=esm', '--target=' + target].concat(minify), {
|
||||
'in.js': `
|
||||
function foo() {
|
||||
let x
|
||||
x ??= {}
|
||||
return 123
|
||||
}
|
||||
if (foo() !== 123) throw 'fail'
|
||||
`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let simpleCyclicImportTestCase542 = {
|
||||
'in.js': `
|
||||
import {Test} from './lib';
|
||||
|
||||
@@ -5,11 +5,9 @@ const fs = require('fs')
|
||||
const os = require('os')
|
||||
|
||||
const repoDir = path.dirname(__dirname)
|
||||
const denoDir = path.join(repoDir, 'deno')
|
||||
const npmDir = path.join(repoDir, 'npm', 'esbuild')
|
||||
const version = fs.readFileSync(path.join(repoDir, 'version.txt'), 'utf8').trim()
|
||||
const nodeTarget = 'node10'; // See: https://nodejs.org/en/about/releases/
|
||||
const denoTarget = 'deno1'; // See: https://docs.deno.com/runtime/fundamentals/stability_and_releases/
|
||||
const umdBrowserTarget = 'es2015'; // Transpiles "async"
|
||||
const esmBrowserTarget = 'es2017'; // Preserves "async"
|
||||
|
||||
@@ -271,46 +269,6 @@ exports.buildWasmLib = async (esbuildPath) => {
|
||||
}
|
||||
}
|
||||
|
||||
const buildDenoLib = async (esbuildPath) => {
|
||||
// Generate "deno/mod.js"
|
||||
const packageJSON = JSON.parse(fs.readFileSync(path.join(npmDir, 'package.json'), 'utf8'))
|
||||
childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'deno', 'mod.ts'),
|
||||
'--bundle',
|
||||
'--outfile=' + path.join(denoDir, 'mod.js'),
|
||||
'--target=' + denoTarget,
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--define:ESBUILD_BINARY_HASHES=' + JSON.stringify(packageJSON['esbuild.binaryHashes'], null, 2),
|
||||
'--platform=neutral',
|
||||
'--log-level=warning',
|
||||
'--banner:js=/// <reference types="./mod.d.ts" />',
|
||||
], { cwd: repoDir })
|
||||
|
||||
// Generate "deno/wasm.js"
|
||||
const GOROOT = childProcess.execFileSync('go', ['env', 'GOROOT']).toString().trim()
|
||||
let wasm_exec_js = fs.readFileSync(path.join(GOROOT, 'lib', 'wasm', 'wasm_exec.js'), 'utf8')
|
||||
const wasmWorkerCode = await generateWorkerCode({ esbuildPath, wasm_exec_js, minify: true, target: denoTarget })
|
||||
const modWASM = childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'deno', 'wasm.ts'),
|
||||
'--bundle',
|
||||
'--target=' + denoTarget,
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCode),
|
||||
'--platform=neutral',
|
||||
'--log-level=warning',
|
||||
'--banner:js=/// <reference types="./wasm.d.ts" />',
|
||||
], { cwd: repoDir }).toString().replace('WEB_WORKER_FUNCTION', wasmWorkerCode)
|
||||
fs.writeFileSync(path.join(denoDir, 'wasm.js'), modWASM)
|
||||
|
||||
// Generate "deno/mod.d.ts"
|
||||
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'shared', 'types.ts'), 'utf8')
|
||||
fs.writeFileSync(path.join(denoDir, 'mod.d.ts'), types_ts)
|
||||
fs.writeFileSync(path.join(denoDir, 'wasm.d.ts'), types_ts)
|
||||
|
||||
// And copy the WebAssembly file over to the Deno library as well
|
||||
fs.copyFileSync(path.join(repoDir, 'npm', 'esbuild-wasm', 'esbuild.wasm'), path.join(repoDir, 'deno', 'esbuild.wasm'))
|
||||
}
|
||||
|
||||
// Writing a file atomically is important for watch mode tests since we don't
|
||||
// want to read the file after it has been truncated but before the new contents
|
||||
// have been written.
|
||||
@@ -402,7 +360,6 @@ exports.dirname = __dirname
|
||||
// The main Makefile invokes this script before publishing
|
||||
if (require.main === module) {
|
||||
if (process.argv.indexOf('--wasm') >= 0) exports.buildWasmLib(process.argv[2])
|
||||
else if (process.argv.indexOf('--deno') >= 0) buildDenoLib(process.argv[2])
|
||||
else if (process.argv.indexOf('--version') >= 0) updateVersionPackageJSON(process.argv[2])
|
||||
else if (process.argv.indexOf('--neutral') >= 0) buildNeutralLib(process.argv[2])
|
||||
else if (process.argv.indexOf('--update-version-go') >= 0) updateVersionGo()
|
||||
|
||||
+67
-1
@@ -77,6 +77,33 @@ let buildTests = {
|
||||
}
|
||||
},
|
||||
|
||||
async doNotOverwriteInputFiles({ esbuild, testDir }) {
|
||||
let input = path.join(testDir, 'input.js')
|
||||
await writeFileAsync(input, 'x=1+2')
|
||||
|
||||
try {
|
||||
await esbuild.build({
|
||||
entryPoints: [input],
|
||||
outfile: input,
|
||||
logLevel: 'silent',
|
||||
})
|
||||
throw new Error('Expected build failure')
|
||||
} catch (e) {
|
||||
if (!e.errors || !e.errors[0] || !e.errors[0].text.startsWith('Refusing to overwrite input file')) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
assert.strictEqual(await readFileAsync(input, 'utf8'), 'x=1+2')
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: [input],
|
||||
outfile: input,
|
||||
logLevel: 'silent',
|
||||
allowOverwrite: true,
|
||||
})
|
||||
assert.strictEqual(await readFileAsync(input, 'utf8'), 'x = 1 + 2;\n')
|
||||
},
|
||||
|
||||
async mangleCacheBuild({ esbuild }) {
|
||||
var result = await esbuild.build({
|
||||
stdin: {
|
||||
@@ -4945,6 +4972,7 @@ let serveTests = {
|
||||
const big = path.join(testDir, 'big.txt')
|
||||
const byteCount = 16 * 1024 * 1024
|
||||
const buffer = require('crypto').randomBytes(byteCount)
|
||||
for (let i = 0; i < 1; i++) buffer[i] = 0xFF * (i & 1) // Make sure the MIME type is "application/octet-stream"
|
||||
await writeFileAsync(big, buffer)
|
||||
|
||||
const context = await esbuild.context({});
|
||||
@@ -7589,12 +7617,50 @@ let formatTests = {
|
||||
const messages = await esbuild.formatMessages([
|
||||
{ text: 'This is an error' },
|
||||
{ text: 'Another error', location: { file: 'file.js' } },
|
||||
{ text: 'Error with line and column', location: { file: 'file.js', line: 3, column: 6 } },
|
||||
{ text: 'Error with source code', location: { file: 'file.js', line: 3, column: 6, length: 5, lineText: 'foo = 1 + 2;' } },
|
||||
], {
|
||||
kind: 'error',
|
||||
})
|
||||
assert.strictEqual(messages.length, 2)
|
||||
assert.strictEqual(messages.length, 4)
|
||||
assert.strictEqual(messages[0], `${errorIcon} [ERROR] This is an error\n\n`)
|
||||
assert.strictEqual(messages[1], `${errorIcon} [ERROR] Another error\n\n file.js:0:0:\n 0 │ \n ╵ ^\n\n`)
|
||||
assert.strictEqual(messages[2], `${errorIcon} [ERROR] Error with line and column\n\n file.js:3:6:\n 3 │ \n ╵ ^\n\n`)
|
||||
assert.strictEqual(messages[3], `${errorIcon} [ERROR] Error with source code\n\n file.js:3:6:\n 3 │ foo = 1 + 2;\n ╵ ~~~~~\n\n`)
|
||||
},
|
||||
|
||||
async formatMessagesVisualStudio({ esbuild }) {
|
||||
const messages = await esbuild.formatMessages([
|
||||
{ text: 'This is an error' },
|
||||
{ text: 'Another error', location: { file: 'file.js' } },
|
||||
{ text: 'Error with line and column', location: { file: 'file.js', line: 3, column: 6 } },
|
||||
{ text: 'Error with source code', location: { file: 'file.js', line: 3, column: 6, length: 5, lineText: 'foo = 1 + 2;' } },
|
||||
], {
|
||||
kind: 'error',
|
||||
logStyle: 'visualstudio',
|
||||
})
|
||||
assert.strictEqual(messages.length, 4)
|
||||
assert.strictEqual(messages[0], `esbuild: error ES0000: This is an error\n`)
|
||||
assert.strictEqual(messages[1], `file.js: error ES0000: Another error\n`)
|
||||
assert.strictEqual(messages[2], `file.js(3,7): error ES0000: Error with line and column\n`)
|
||||
assert.strictEqual(messages[3], `file.js(3,7): error ES0000: Error with source code\n`)
|
||||
},
|
||||
|
||||
async formatMessagesClang({ esbuild }) {
|
||||
const messages = await esbuild.formatMessages([
|
||||
{ text: 'This is an error' },
|
||||
{ text: 'Another error', location: { file: 'file.js' } },
|
||||
{ text: 'Error with line and column', location: { file: 'file.js', line: 3, column: 6 } },
|
||||
{ text: 'Error with source code', location: { file: 'file.js', line: 3, column: 6, length: 5, lineText: 'foo = 1 + 2;' } },
|
||||
], {
|
||||
kind: 'error',
|
||||
logStyle: 'clang',
|
||||
})
|
||||
assert.strictEqual(messages.length, 4)
|
||||
assert.strictEqual(messages[0], `error: This is an error\n`)
|
||||
assert.strictEqual(messages[1], `file.js: error: Another error\n`)
|
||||
assert.strictEqual(messages[2], `file.js:3:7: error: Error with line and column\n\n^\n`)
|
||||
assert.strictEqual(messages[3], `file.js:3:7: error: Error with source code\nfoo = 1 + 2;\n ~~~~~\n`)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.28.1
|
||||
0.28.2
|
||||
|
||||
Reference in New Issue
Block a user