mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
put on github
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
/esbuild
|
||||
/npm/esbuild-darwin-64/bin/esbuild
|
||||
/npm/esbuild-linux-64/bin/esbuild
|
||||
/npm/esbuild-wasm/esbuild.wasm
|
||||
/npm/esbuild-wasm/wasm_exec.js
|
||||
/npm/esbuild-windows-64/esbuild.exe
|
||||
bench/
|
||||
demo/
|
||||
github/
|
||||
node_modules/
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Evan Wallace
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,163 @@
|
||||
esbuild: src/esbuild/*/*.go
|
||||
GOPATH=`pwd` go build -o esbuild esbuild/main
|
||||
|
||||
test:
|
||||
GOPATH=`pwd` go test ./...
|
||||
|
||||
platform-all: test
|
||||
make -j4 platform-windows platform-darwin platform-linux platform-wasm
|
||||
|
||||
platform-windows:
|
||||
mkdir -p npm/esbuild-windows-64/bin
|
||||
GOOS=windows GOARCH=amd64 GOPATH=`pwd` go build -o npm/esbuild-windows-64/esbuild.exe esbuild/main
|
||||
|
||||
platform-darwin:
|
||||
mkdir -p npm/esbuild-darwin-64/bin
|
||||
GOOS=darwin GOARCH=amd64 GOPATH=`pwd` go build -o npm/esbuild-darwin-64/bin/esbuild esbuild/main
|
||||
|
||||
platform-linux:
|
||||
mkdir -p npm/esbuild-linux-64/bin
|
||||
GOOS=linux GOARCH=amd64 GOPATH=`pwd` go build -o npm/esbuild-linux-64/bin/esbuild esbuild/main
|
||||
|
||||
platform-wasm:
|
||||
GOOS=js GOARCH=wasm GOPATH=`pwd` go build -o npm/esbuild-wasm/esbuild.wasm esbuild/main
|
||||
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" npm/esbuild-wasm/wasm_exec.js
|
||||
|
||||
publish-all: platform-all
|
||||
make -j4 publish-windows publish-darwin publish-linux publish-wasm
|
||||
|
||||
publish-windows: platform-windows
|
||||
[ ! -z "$(OTP)" ] && cd npm/esbuild-windows-64 && npm publish --otp="$(OTP)"
|
||||
|
||||
publish-darwin: platform-darwin
|
||||
[ ! -z "$(OTP)" ] && cd npm/esbuild-darwin-64 && npm publish --otp="$(OTP)"
|
||||
|
||||
publish-linux: platform-linux
|
||||
[ ! -z "$(OTP)" ] && cd npm/esbuild-linux-64 && npm publish --otp="$(OTP)"
|
||||
|
||||
publish-wasm: platform-wasm
|
||||
[ ! -z "$(OTP)" ] && cd npm/esbuild-wasm && npm publish --otp="$(OTP)"
|
||||
|
||||
clean:
|
||||
rm -f esbuild npm/esbuild-wasm/esbuild.wasm npm/esbuild-wasm/wasm_exec.js
|
||||
rm -f npm/esbuild-windows-64/esbuild.exe
|
||||
rm -rf npm/esbuild-darwin-64/bin
|
||||
rm -rf npm/esbuild-linux-64/bin
|
||||
|
||||
################################################################################
|
||||
|
||||
github/test262:
|
||||
mkdir -p github
|
||||
git clone [email protected]:tc39/test262.git github/test262
|
||||
|
||||
demo/test262: | github/test262
|
||||
mkdir -p demo/test262
|
||||
cp -r github/test262/test demo/test262/test
|
||||
|
||||
################################################################################
|
||||
|
||||
test262: esbuild | demo/test262
|
||||
node scripts/test262.js
|
||||
|
||||
################################################################################
|
||||
|
||||
ROLLUP_CONFIG += import { terser } from 'rollup-plugin-terser';
|
||||
ROLLUP_CONFIG += export default {
|
||||
ROLLUP_CONFIG += output: { format: 'iife', name: 'THREE', sourcemap: true },
|
||||
ROLLUP_CONFIG += plugins: [terser()],
|
||||
ROLLUP_CONFIG += }
|
||||
|
||||
WEBPACK_FLAGS += --devtool=sourcemap
|
||||
WEBPACK_FLAGS += --mode=production
|
||||
WEBPACK_FLAGS += --output-library THREE
|
||||
|
||||
PARCEL_FLAGS += --global THREE
|
||||
PARCEL_FLAGS += --no-autoinstall
|
||||
PARCEL_FLAGS += --out-dir .
|
||||
PARCEL_FLAGS += --public-url ./
|
||||
|
||||
node_modules/.bin/rollup:
|
||||
npm ci
|
||||
|
||||
node_modules/.bin/webpack:
|
||||
npm ci
|
||||
|
||||
node_modules/.bin/parcel:
|
||||
npm ci
|
||||
|
||||
################################################################################
|
||||
|
||||
github/three:
|
||||
mkdir -p github
|
||||
git clone https://github.com/mrdoob/three.js.git github/three
|
||||
cd github/three && git checkout f0a4e02cb7f2003229c9d1b7af2723935b0643a7
|
||||
|
||||
demo/three: | github/three
|
||||
mkdir -p demo/three
|
||||
cp -r github/three/src demo/three/src
|
||||
|
||||
bench/three: | github/three
|
||||
mkdir -p bench/three
|
||||
echo > bench/three/entry.js
|
||||
for i in {1..10}; do test -d "bench/three/copy$$i" || cp -r github/three/src "bench/three/copy$$i"; done
|
||||
for i in {1..10}; do echo "import * as copy$$i from './copy$$i/Three.js'; export {copy$$i}" >> bench/three/entry.js; done
|
||||
|
||||
################################################################################
|
||||
|
||||
demo-three: demo-three-esbuild demo-three-rollup demo-three-webpack demo-three-parcel
|
||||
|
||||
demo-three-esbuild: esbuild | demo/three
|
||||
rm -fr demo/three/esbuild
|
||||
mkdir -p demo/three/esbuild
|
||||
cd demo/three/esbuild && time -p ../../../esbuild --bundle --name=THREE --sourcemap --minify ../src/Three.js --outfile=Three.esbuild.js
|
||||
du -h demo/three/esbuild/Three.esbuild.js*
|
||||
|
||||
demo-three-rollup: node_modules/.bin/rollup | demo/three
|
||||
rm -fr demo/three/rollup
|
||||
mkdir -p demo/three/rollup
|
||||
echo "$(ROLLUP_CONFIG)" > demo/three/rollup/config.js
|
||||
cd demo/three/rollup && time -p ../../../node_modules/.bin/rollup ../src/Three.js -o Three.rollup.js -c config.js
|
||||
du -h demo/three/rollup/Three.rollup.js*
|
||||
|
||||
demo-three-webpack: node_modules/.bin/webpack | demo/three
|
||||
rm -fr demo/three/webpack node_modules/.cache/terser-webpack-plugin
|
||||
mkdir -p demo/three/webpack
|
||||
cd demo/three/webpack && time -p ../../../node_modules/.bin/webpack ../src/Three.js $(WEBPACK_FLAGS) -o Three.webpack.js
|
||||
du -h demo/three/webpack/Three.webpack.js*
|
||||
|
||||
demo-three-parcel: node_modules/.bin/parcel | demo/three
|
||||
rm -fr demo/three/parcel
|
||||
mkdir -p demo/three/parcel
|
||||
cd demo/three/parcel && time -p ../../../node_modules/.bin/parcel build ../src/Three.js $(PARCEL_FLAGS) --out-file Three.parcel.js
|
||||
du -h demo/three/parcel/Three.parcel.js*
|
||||
|
||||
################################################################################
|
||||
|
||||
bench-three: bench-three-esbuild bench-three-rollup bench-three-webpack bench-three-parcel
|
||||
echo 'Line count:'
|
||||
find bench/three -name '*.js' | xargs wc -l | tail -n 1
|
||||
|
||||
bench-three-esbuild: esbuild | bench/three
|
||||
rm -fr bench/three/esbuild
|
||||
mkdir -p bench/three/esbuild
|
||||
cd bench/three/esbuild && time -p ../../../esbuild --bundle --name=THREE --sourcemap --minify ../entry.js --outfile=entry.esbuild.js
|
||||
du -h bench/three/esbuild/entry.esbuild.js*
|
||||
|
||||
bench-three-rollup: node_modules/.bin/rollup | bench/three
|
||||
rm -fr bench/three/rollup
|
||||
mkdir -p bench/three/rollup
|
||||
echo "$(ROLLUP_CONFIG)" > bench/three/rollup/config.js
|
||||
cd bench/three/rollup && time -p ../../../node_modules/.bin/rollup ../entry.js -o entry.rollup.js -c config.js
|
||||
du -h bench/three/rollup/entry.rollup.js*
|
||||
|
||||
bench-three-webpack: node_modules/.bin/webpack | bench/three
|
||||
rm -fr bench/three/webpack node_modules/.cache/terser-webpack-plugin
|
||||
mkdir -p bench/three/webpack
|
||||
cd bench/three/webpack && time -p ../../../node_modules/.bin/webpack ../entry.js $(WEBPACK_FLAGS) -o entry.webpack.js
|
||||
du -h bench/three/webpack/entry.webpack.js*
|
||||
|
||||
bench-three-parcel: node_modules/.bin/parcel | bench/three
|
||||
rm -fr bench/three/parcel
|
||||
mkdir -p bench/three/parcel
|
||||
cd bench/three/parcel && time -p ../../../node_modules/.bin/parcel build ../entry.js $(PARCEL_FLAGS) --out-file entry.parcel.js
|
||||
du -h bench/three/parcel/entry.parcel.js*
|
||||
@@ -0,0 +1,97 @@
|
||||
# esbuild
|
||||
|
||||
This is a JavaScript bundler and minifier. It packages up JavaScript code for distribution on the web.
|
||||
|
||||
## Why?
|
||||
|
||||
Why build another JavaScript build tool? The current build tools for the web are at least an order of magnitude slower than they should be. I'm hoping that this project serves as an "existence proof" that our JavaScript tooling can be much, much faster.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
The use case I have in mind is packaging a large codebase for production. This includes minifying the code, which reduces network transfer time, and producing source maps, which are important for debugging errors in production. Ideally the build tool should also build quickly without having to warm up a cache first.
|
||||
|
||||
My main benchmark approximates a large codebase by duplicating the [three.js](https://github.com/mrdoob/three.js) library 10 times and building a single bundle from scratch, without any caches. For this benchmark, esbuild is **10-100x faster** than the other JavaScript bundlers I tested (webpack, rollup, and parcel). The benchmark can be run with `make bench-three`.
|
||||
|
||||

|
||||
|
||||
| Bundler | Time | Relative slowdown | Absolute speed | Output size |
|
||||
| :-------------- | ------: | ----------------: | -------------: | ----------: |
|
||||
| esbuild | 0.58s | 1x | 1028 kloc/s | 5.83mb |
|
||||
| rollup + terser | 43.56s | 75x | 14 kloc/s | 5.96mb |
|
||||
| webpack | 47.85s | 83x | 12 kloc/s | 5.82mb |
|
||||
| parcel | 120.45s | 208x | 5 kloc/s | 5.90mb |
|
||||
|
||||
I'm running esbuild with `--bundle --minify --sourcemap`. I used the `rollup-plugin-terser` plugin because rollup itself doesn't support minification. Webpack uses `--mode=production --devtool=sourcemap`. Parcel uses the default options. The tests were done on a 6-core 2019 MacBook Pro with 16gb of RAM.
|
||||
|
||||
## Why is it fast?
|
||||
|
||||
Several reasons:
|
||||
|
||||
* It's written in Go, a language that compiles to native code
|
||||
* Parsing, printing, and source map generation are all fully parallelized
|
||||
* Everything is done in very few passes without expensive data transformations
|
||||
* Code is written with speed in mind, and tries to avoid unnecessary allocations
|
||||
|
||||
## Status
|
||||
|
||||
Currently supported:
|
||||
|
||||
* CommonJS modules
|
||||
* ES6 modules
|
||||
* Bundling with static binding of ES6 modules using `--bundle`
|
||||
* Full minification with `--minify` (whitespace, identifiers, and mangling)
|
||||
* Full source map support when `--sourcemap` is enabled
|
||||
* JSX-to-JavaScript conversion for `.jsx` files
|
||||
* Compile-time identifier substitutions via `--define`
|
||||
|
||||
This is a hobby project that I wrote over the 2019-2020 winter break. I believe that it's relatively complete and functional. However, it's brand new code and probably has a lot of bugs. It also hasn't yet been used in production by anyone. Use at your own risk.
|
||||
|
||||
Also keep in mind that this doesn't currently do any lowering to e.g. ES6 or ES5. All syntax is passed through unmodified so the latest JavaScript features won't work in older browsers. This is easy to add but I haven't gotten to it yet.
|
||||
|
||||
I don't personally want to run a large open source project, so I'm not looking for contributions at this time.
|
||||
|
||||
## Install
|
||||
|
||||
The executable can be built using `make`, assuming you have the Go language toolchain installed. Prebuilt binaries are currently available on npm under separate packages:
|
||||
|
||||
```
|
||||
npm install -g esbuild-linux-64 # for Linux
|
||||
npm install -g esbuild-darwin-64 # for macOS
|
||||
npm install -g esbuild-windows-64 # for Windows
|
||||
npm install -g esbuild-wasm # for all other platforms
|
||||
```
|
||||
|
||||
This adds a command called `esbuild`.
|
||||
|
||||
## Usage
|
||||
|
||||
The command-line interface takes a list of entry points and produces one bundle file per entry point. Here are the available options:
|
||||
|
||||
```
|
||||
Usage:
|
||||
esbuild [options] [entry points]
|
||||
|
||||
Options:
|
||||
--name=... The name of the module
|
||||
--bundle Bundle all dependencies into the output files
|
||||
--outfile=... The output file (for one entry point)
|
||||
--outdir=... The output directory (for multiple entry points)
|
||||
--sourcemap Emit a source map
|
||||
--error-limit=... Maximum error count or 0 to disable (default 10)
|
||||
|
||||
--minify Sets all --minify-* flags
|
||||
--minify-whitespace Remove whitespace
|
||||
--minify-identifiers Shorten identifiers
|
||||
--minify-syntax Use equivalent but shorter syntax
|
||||
|
||||
--define:K=V Substitute K with V while parsing
|
||||
--jsx-factory=... What to use instead of React.createElement
|
||||
--jsx-fragment=... What to use instead of React.Fragment
|
||||
|
||||
--trace=... Write a CPU trace to this file
|
||||
--cpuprofile=... Write a CPU profile to this file
|
||||
|
||||
Example:
|
||||
# Produces dist/entry_point.js and dist/entry_point.js.map
|
||||
esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "esbuild-darwin-64",
|
||||
"version": "0.0.3",
|
||||
"description": "The macOS 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": "https://github.com/evanw/esbuild",
|
||||
"license": "MIT",
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"directories": {
|
||||
"bin": "bin"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "esbuild-linux-64",
|
||||
"version": "0.0.3",
|
||||
"description": "The Linux 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": "https://github.com/evanw/esbuild",
|
||||
"license": "MIT",
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"directories": {
|
||||
"bin": "bin"
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Forward to the automatically-generated WebAssembly loader from the Go compiler
|
||||
const wasm_exec = require.resolve('esbuild-wasm/wasm_exec.js');
|
||||
const esbuild_wasm = require.resolve('esbuild-wasm/esbuild.wasm');
|
||||
const code = require('fs').readFileSync(wasm_exec, 'utf8');
|
||||
const wrapper = new Function('require', 'process', code);
|
||||
const argv = ['node', wasm_exec, esbuild_wasm].concat(process.argv.slice(2));
|
||||
wrapper(require, Object.assign(Object.create(process), { argv }));
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "esbuild-wasm",
|
||||
"version": "0.0.3",
|
||||
"description": "The cross-platform WebAssembly binary for esbuild, a JavaScript bundler.",
|
||||
"repository": "https://github.com/evanw/esbuild",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"directories": {
|
||||
"bin": "bin"
|
||||
}
|
||||
}
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Unfortunately even though npm shims "bin" commands on Windows with auto-
|
||||
// generated forwarding scripts, it doesn't strip the ".exe" from the file name
|
||||
// first. So it's possible to publish executables via npm on all platforms
|
||||
// except Windows. I consider this a npm bug.
|
||||
//
|
||||
// My workaround is to add this script as another layer of indirection. It'll
|
||||
// be slower because node has to boot up just to shell out to the actual exe,
|
||||
// but Windows is somewhat of a second-class platform to npm so it's the best
|
||||
// I can do I think.
|
||||
const esbuild_exe = require.resolve('esbuild-windows-64/esbuild.exe');
|
||||
const child_process = require('child_process');
|
||||
child_process.spawnSync(esbuild_exe, process.argv.slice(2), { stdio: 'inherit' });
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "esbuild-windows-64",
|
||||
"version": "0.0.3",
|
||||
"description": "The Windows 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": "https://github.com/evanw/esbuild",
|
||||
"license": "MIT",
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"directories": {
|
||||
"bin": "bin"
|
||||
}
|
||||
}
|
||||
Generated
+8528
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"js-yaml": "3.13.1",
|
||||
"parcel": "1.12.4",
|
||||
"rollup": "1.20.3",
|
||||
"rollup-plugin-terser": "5.1.3",
|
||||
"webpack": "4.39.3",
|
||||
"webpack-cli": "3.3.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const jsYaml = require('js-yaml');
|
||||
const child_process = require('child_process');
|
||||
|
||||
function findFiles() {
|
||||
function visit(dir) {
|
||||
for (const entry of fs.readdirSync(dir)) {
|
||||
const fullEntry = path.join(dir, entry);
|
||||
const stats = fs.statSync(fullEntry);
|
||||
if (stats.isDirectory()) {
|
||||
visit(fullEntry);
|
||||
} else if (stats.isFile() && entry.endsWith('.js') && !entry.endsWith('_FIXTURE.js')) {
|
||||
files.push(fullEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const files = [];
|
||||
visit(path.join(__dirname, '..', 'demo', 'test262', 'test'));
|
||||
return files;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const esbuild = path.join(__dirname, '..', 'esbuild');
|
||||
const files = findFiles();
|
||||
let runCount = 0;
|
||||
let shouldHavePassed = 0;
|
||||
let shouldHaveFailed = 0;
|
||||
let reparseCount = 0;
|
||||
let reprintCount = 0;
|
||||
|
||||
function esbuildFile(file, outfile) {
|
||||
const child = child_process.spawn(esbuild, [
|
||||
file,
|
||||
`--outfile=${outfile}`,
|
||||
], {
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
});
|
||||
let done = false;
|
||||
const promise = new Promise(resolve => {
|
||||
const chunks = [];
|
||||
child.stderr.on('data', chunk => chunks.push(chunk));
|
||||
child.on('close', status => {
|
||||
done = true;
|
||||
resolve({
|
||||
status,
|
||||
stderr: Buffer.concat(chunks).toString(),
|
||||
});
|
||||
})
|
||||
});
|
||||
const timeout = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
if (!done) {
|
||||
console.log(`${file}: TIMED OUT!`);
|
||||
child.kill();
|
||||
reject();
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
return Promise.race([promise, timeout]);
|
||||
}
|
||||
|
||||
async function processFile(file) {
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
const start = content.indexOf('/*---');
|
||||
const end = content.indexOf('---*/');
|
||||
if (start < 0 || end < 0) {
|
||||
console.warn(`Missing YAML metadata: ${file}`);
|
||||
return;
|
||||
}
|
||||
const yaml = jsYaml.safeLoad(content.slice(start + 5, end));
|
||||
const shouldWork = !yaml.negative;
|
||||
|
||||
if (yaml.features) {
|
||||
if (yaml.features.includes('class-fields-private')) return
|
||||
if (yaml.features.includes('class-fields-public')) return
|
||||
if (yaml.features.includes('class-methods-private')) return
|
||||
if (yaml.features.includes('class-methods-public')) return
|
||||
if (yaml.features.includes('class-static-fields-private')) return
|
||||
if (yaml.features.includes('class-static-fields-public')) return
|
||||
if (yaml.features.includes('class-static-methods-private')) return
|
||||
if (yaml.features.includes('class-static-methods-public')) return
|
||||
if (yaml.features.includes('hashbang')) return
|
||||
if (yaml.features.includes('regexp-match-indices')) return
|
||||
if (yaml.features.includes('regexp-named-groups')) return
|
||||
if (yaml.features.includes('regexp-unicode-property-escapes')) return
|
||||
if (yaml.features.includes('top-level-await')) return
|
||||
}
|
||||
|
||||
const hash = crypto.createHash('md5').update(file).digest('hex');
|
||||
const output1 = path.join(process.env.HOME, '.Trash', hash + '-1.js');
|
||||
const output2 = path.join(process.env.HOME, '.Trash', hash + '-2.js');
|
||||
const result = await esbuildFile(file, output1);
|
||||
const worked = result.status === 0;
|
||||
|
||||
if (worked !== shouldWork) {
|
||||
if (!worked) shouldHavePassed++;
|
||||
else shouldHaveFailed++;
|
||||
if (!result.stderr.includes(path.basename(file))) {
|
||||
console.log(`${file}: error: ${(yaml.description || '').trim()}`);
|
||||
} else {
|
||||
process.stdout.write(result.stderr);
|
||||
}
|
||||
} else if (worked) {
|
||||
const result2 = await esbuildFile(output1, output2);
|
||||
if (result2.status !== 0) {
|
||||
console.log(`!!! REPARSE ERROR: ${file} !!!`);
|
||||
process.stdout.write(result.stderr);
|
||||
reparseCount++;
|
||||
} else if (fs.readFileSync(output1, 'utf8') !== fs.readFileSync(output2, 'utf8')) {
|
||||
console.log(`!!! REPRINT ERROR: ${file} !!!`);
|
||||
reprintCount++;
|
||||
}
|
||||
}
|
||||
runCount++;
|
||||
}
|
||||
|
||||
// Process tests in parallel for speed
|
||||
await new Promise((resolve, reject) => {
|
||||
let inFlight = 0;
|
||||
let i = 0;
|
||||
|
||||
function next() {
|
||||
if (i === files.length && inFlight === 0) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
while (i < files.length && inFlight < 5) {
|
||||
inFlight++;
|
||||
processFile(files[i++]).then(() => {
|
||||
inFlight--;
|
||||
next();
|
||||
}, reject);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
console.log(`tests ran: ${runCount}`);
|
||||
console.log(` tests incorrectly failed: ${shouldHavePassed}`);
|
||||
console.log(` tests incorrectly passed: ${shouldHaveFailed}`);
|
||||
console.log(`tests skipped: ${files.length - runCount}`);
|
||||
console.log(`reparse failures: ${reparseCount}`);
|
||||
console.log(`reprint failures: ${reprintCount}`);
|
||||
}
|
||||
|
||||
main().catch(e => setTimeout(() => {
|
||||
throw e
|
||||
}));
|
||||
@@ -0,0 +1,947 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Every module (i.e. file) is parsed into a separate AST data structure. For
|
||||
// efficiency, the parser also resolves all scopes and binds all symbols in the
|
||||
// tree.
|
||||
//
|
||||
// Identifiers in the tree are referenced by a Ref, which is a pointer into the
|
||||
// symbol table for the file. The symbol table is stored as a top-level field
|
||||
// in the AST so it can be accessed without traversing the tree. For example,
|
||||
// a renaming pass can iterate over the symbol table without touching the tree.
|
||||
//
|
||||
// Parse trees are intended to be immutable. That makes it easy to build an
|
||||
// incremental compiler with a "watch" mode that can avoid re-parsing files
|
||||
// that have already been parsed. Any passes that operate on an AST after it
|
||||
// has been parsed should create a copy of the mutated parts of the tree
|
||||
// instead of mutating the original tree.
|
||||
|
||||
type L int
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
|
||||
const (
|
||||
LLowest L = iota
|
||||
LComma
|
||||
LSpread
|
||||
LYield
|
||||
LAssign
|
||||
LConditional
|
||||
LNullishCoalescing
|
||||
LLogicalOr
|
||||
LLogicalAnd
|
||||
LBitwiseOr
|
||||
LBitwiseXor
|
||||
LBitwiseAnd
|
||||
LEquals
|
||||
LCompare
|
||||
LShift
|
||||
LAdd
|
||||
LMultiply
|
||||
LExponentiation
|
||||
LPrefix
|
||||
LPostfix
|
||||
LNew
|
||||
LCall
|
||||
)
|
||||
|
||||
type OpCode int
|
||||
|
||||
func (op OpCode) IsPrefix() bool {
|
||||
return op < UnOpPostDec
|
||||
}
|
||||
|
||||
func (op OpCode) IsLeftAssociative() bool {
|
||||
return op >= BinOpAdd && op < BinOpComma && op != BinOpPow
|
||||
}
|
||||
|
||||
func (op OpCode) IsRightAssociative() bool {
|
||||
return op >= BinOpAssign || op == BinOpPow
|
||||
}
|
||||
|
||||
// If you add a new token, remember to add it to "OpTable" too
|
||||
const (
|
||||
// Prefix
|
||||
UnOpPos OpCode = iota
|
||||
UnOpNeg
|
||||
UnOpCpl
|
||||
UnOpNot
|
||||
UnOpPreDec
|
||||
UnOpPreInc
|
||||
UnOpVoid
|
||||
UnOpTypeOf
|
||||
UnOpDelete
|
||||
|
||||
// Postfix
|
||||
UnOpPostDec
|
||||
UnOpPostInc
|
||||
|
||||
// Left-associative
|
||||
BinOpAdd
|
||||
BinOpSub
|
||||
BinOpMul
|
||||
BinOpDiv
|
||||
BinOpRem
|
||||
BinOpPow
|
||||
BinOpLt
|
||||
BinOpLe
|
||||
BinOpGt
|
||||
BinOpGe
|
||||
BinOpIn
|
||||
BinOpInstanceOf
|
||||
BinOpShl
|
||||
BinOpShr
|
||||
BinOpUShr
|
||||
BinOpLooseEq
|
||||
BinOpLooseNe
|
||||
BinOpStrictEq
|
||||
BinOpStrictNe
|
||||
BinOpNullishCoalescing
|
||||
BinOpLogicalOr
|
||||
BinOpLogicalAnd
|
||||
BinOpBitwiseOr
|
||||
BinOpBitwiseAnd
|
||||
BinOpBitwiseXor
|
||||
|
||||
// Non-associative
|
||||
BinOpComma
|
||||
|
||||
// Right-associative
|
||||
BinOpAssign
|
||||
BinOpAddAssign
|
||||
BinOpSubAssign
|
||||
BinOpMulAssign
|
||||
BinOpDivAssign
|
||||
BinOpRemAssign
|
||||
BinOpPowAssign
|
||||
BinOpShlAssign
|
||||
BinOpShrAssign
|
||||
BinOpUShrAssign
|
||||
BinOpBitwiseOrAssign
|
||||
BinOpBitwiseAndAssign
|
||||
BinOpBitwiseXorAssign
|
||||
)
|
||||
|
||||
type opTableEntry struct {
|
||||
Text string
|
||||
Level L
|
||||
IsKeyword bool
|
||||
}
|
||||
|
||||
var OpTable = []opTableEntry{
|
||||
// Prefix
|
||||
{"+", LPrefix, false},
|
||||
{"-", LPrefix, false},
|
||||
{"~", LPrefix, false},
|
||||
{"!", LPrefix, false},
|
||||
{"--", LPrefix, false},
|
||||
{"++", LPrefix, false},
|
||||
{"void", LPrefix, true},
|
||||
{"typeof", LPrefix, true},
|
||||
{"delete", LPrefix, true},
|
||||
|
||||
// Postfix
|
||||
{"--", LPostfix, false},
|
||||
{"++", LPostfix, false},
|
||||
|
||||
// Left-associative
|
||||
{"+", LAdd, false},
|
||||
{"-", LAdd, false},
|
||||
{"*", LMultiply, false},
|
||||
{"/", LMultiply, false},
|
||||
{"%", LMultiply, false},
|
||||
{"**", LExponentiation, false}, // Right-associative
|
||||
{"<", LCompare, false},
|
||||
{"<=", LCompare, false},
|
||||
{">", LCompare, false},
|
||||
{">=", LCompare, false},
|
||||
{"in", LCompare, true},
|
||||
{"instanceof", LCompare, true},
|
||||
{"<<", LShift, false},
|
||||
{">>", LShift, false},
|
||||
{">>>", LShift, false},
|
||||
{"==", LEquals, false},
|
||||
{"!=", LEquals, false},
|
||||
{"===", LEquals, false},
|
||||
{"!==", LEquals, false},
|
||||
{"??", LNullishCoalescing, false},
|
||||
{"||", LLogicalOr, false},
|
||||
{"&&", LLogicalAnd, false},
|
||||
{"|", LBitwiseOr, false},
|
||||
{"&", LBitwiseAnd, false},
|
||||
{"^", LBitwiseXor, false},
|
||||
|
||||
// Non-associative
|
||||
{",", LComma, false},
|
||||
|
||||
// Right-associative
|
||||
{"=", LAssign, false},
|
||||
{"+=", LAssign, false},
|
||||
{"-=", LAssign, false},
|
||||
{"*=", LAssign, false},
|
||||
{"/=", LAssign, false},
|
||||
{"%=", LAssign, false},
|
||||
{"**=", LAssign, false},
|
||||
{"<<=", LAssign, false},
|
||||
{">>=", LAssign, false},
|
||||
{">>>=", LAssign, false},
|
||||
{"|=", LAssign, false},
|
||||
{"&=", LAssign, false},
|
||||
{"^=", LAssign, false},
|
||||
}
|
||||
|
||||
type Loc struct {
|
||||
// This is the 0-based index of this location from the start of the file
|
||||
Start int32
|
||||
}
|
||||
|
||||
type Range struct {
|
||||
Loc Loc
|
||||
Len int32
|
||||
}
|
||||
|
||||
type LocRef struct {
|
||||
Loc Loc
|
||||
Ref Ref
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
Loc Loc
|
||||
Text string
|
||||
}
|
||||
|
||||
type PropertyKind int
|
||||
|
||||
const (
|
||||
PropertyNormal PropertyKind = iota
|
||||
PropertyGet
|
||||
PropertySet
|
||||
PropertySpread
|
||||
)
|
||||
|
||||
type Property struct {
|
||||
Kind PropertyKind
|
||||
IsComputed bool
|
||||
IsMethod bool
|
||||
IsStatic bool
|
||||
Key Expr
|
||||
Value Expr
|
||||
|
||||
// This is used when parsing a pattern that uses default values:
|
||||
//
|
||||
// [a = 1] = [];
|
||||
// ({a = 1} = {});
|
||||
//
|
||||
DefaultValue *Expr
|
||||
}
|
||||
|
||||
type PropertyBinding struct {
|
||||
IsComputed bool
|
||||
IsSpread bool
|
||||
Key Expr
|
||||
Value Binding
|
||||
DefaultValue *Expr
|
||||
}
|
||||
|
||||
type Arg struct {
|
||||
Binding Binding
|
||||
Default *Expr
|
||||
}
|
||||
|
||||
type Fn struct {
|
||||
Name *LocRef
|
||||
Args []Arg
|
||||
IsAsync bool
|
||||
IsGenerator bool
|
||||
HasRestArg bool
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type Class struct {
|
||||
Name *LocRef
|
||||
Extends *Expr
|
||||
Properties []Property
|
||||
}
|
||||
|
||||
type ArrayBinding struct {
|
||||
Binding Binding
|
||||
DefaultValue *Expr
|
||||
}
|
||||
|
||||
type Binding struct {
|
||||
Loc Loc
|
||||
Data B
|
||||
}
|
||||
|
||||
// This interface is never called. Its purpose is to encode a variant type in
|
||||
// Go's type system.
|
||||
type B interface{ isBinding() }
|
||||
|
||||
type BMissing struct{}
|
||||
|
||||
type BIdentifier struct{ Ref Ref }
|
||||
|
||||
type BArray struct {
|
||||
Items []ArrayBinding
|
||||
HasSpread bool
|
||||
}
|
||||
|
||||
type BObject struct{ Properties []PropertyBinding }
|
||||
|
||||
func (*BMissing) isBinding() {}
|
||||
func (*BIdentifier) isBinding() {}
|
||||
func (*BArray) isBinding() {}
|
||||
func (*BObject) isBinding() {}
|
||||
|
||||
type Expr struct {
|
||||
Loc Loc
|
||||
Data E
|
||||
}
|
||||
|
||||
// This interface is never called. Its purpose is to encode a variant type in
|
||||
// Go's type system.
|
||||
type E interface{ isExpr() }
|
||||
|
||||
type EArray struct{ Items []Expr }
|
||||
|
||||
type EUnary struct {
|
||||
Op OpCode
|
||||
Value Expr
|
||||
}
|
||||
|
||||
type EBinary struct {
|
||||
Op OpCode
|
||||
Left Expr
|
||||
Right Expr
|
||||
}
|
||||
|
||||
type EBoolean struct{ Value bool }
|
||||
|
||||
type ESuper struct{}
|
||||
|
||||
type ENull struct{}
|
||||
|
||||
type EUndefined struct{}
|
||||
|
||||
type EThis struct{}
|
||||
|
||||
type ENew struct {
|
||||
Target Expr
|
||||
Args []Expr
|
||||
}
|
||||
|
||||
type ENewTarget struct{}
|
||||
|
||||
type EImportMeta struct{}
|
||||
|
||||
type ECall struct {
|
||||
Target Expr
|
||||
Args []Expr
|
||||
IsOptionalChain bool
|
||||
}
|
||||
|
||||
type EDot struct {
|
||||
Target Expr
|
||||
Name string
|
||||
NameLoc Loc
|
||||
IsOptionalChain bool
|
||||
}
|
||||
|
||||
type EIndex struct {
|
||||
Target Expr
|
||||
Index Expr
|
||||
IsOptionalChain bool
|
||||
}
|
||||
|
||||
type EArrow struct {
|
||||
IsAsync bool
|
||||
Args []Arg
|
||||
HasRestArg bool
|
||||
Stmts []Stmt
|
||||
Expr *Expr
|
||||
}
|
||||
|
||||
type EFunction struct{ Fn Fn }
|
||||
|
||||
type EClass struct{ Class Class }
|
||||
|
||||
type EIdentifier struct{ Ref Ref }
|
||||
|
||||
type ENamespaceImport struct {
|
||||
NamespaceRef Ref
|
||||
ItemRef Ref
|
||||
Alias string
|
||||
}
|
||||
|
||||
type EJSXElement struct {
|
||||
Tag *Expr
|
||||
Properties []Property
|
||||
Children []Expr
|
||||
}
|
||||
|
||||
type EMissing struct{}
|
||||
|
||||
type ENumber struct{ Value float64 }
|
||||
|
||||
type EBigInt struct{ Value string }
|
||||
|
||||
type EObject struct{ Properties []Property }
|
||||
|
||||
type ESpread struct{ Value Expr }
|
||||
|
||||
type EString struct{ Value []uint16 }
|
||||
|
||||
type TemplatePart struct {
|
||||
Value Expr
|
||||
Tail []uint16
|
||||
}
|
||||
|
||||
type ETemplate struct {
|
||||
Tag *Expr
|
||||
Head []uint16
|
||||
Parts []TemplatePart
|
||||
}
|
||||
|
||||
type ERegExp struct{ Value string }
|
||||
|
||||
type EAwait struct {
|
||||
Value Expr
|
||||
}
|
||||
|
||||
type EYield struct {
|
||||
Value *Expr
|
||||
IsStar bool
|
||||
}
|
||||
|
||||
type EIf struct {
|
||||
Test Expr
|
||||
Yes Expr
|
||||
No Expr
|
||||
}
|
||||
|
||||
type ERequire struct {
|
||||
Path Path
|
||||
}
|
||||
|
||||
type EImport struct {
|
||||
Path Path
|
||||
}
|
||||
|
||||
func (*EArray) isExpr() {}
|
||||
func (*EUnary) isExpr() {}
|
||||
func (*EBinary) isExpr() {}
|
||||
func (*EBoolean) isExpr() {}
|
||||
func (*ESuper) isExpr() {}
|
||||
func (*ENull) isExpr() {}
|
||||
func (*EUndefined) isExpr() {}
|
||||
func (*EThis) isExpr() {}
|
||||
func (*ENew) isExpr() {}
|
||||
func (*ENewTarget) isExpr() {}
|
||||
func (*EImportMeta) isExpr() {}
|
||||
func (*ECall) isExpr() {}
|
||||
func (*EDot) isExpr() {}
|
||||
func (*EIndex) isExpr() {}
|
||||
func (*EArrow) isExpr() {}
|
||||
func (*EFunction) isExpr() {}
|
||||
func (*EClass) isExpr() {}
|
||||
func (*EIdentifier) isExpr() {}
|
||||
func (*ENamespaceImport) isExpr() {}
|
||||
func (*EJSXElement) isExpr() {}
|
||||
func (*EMissing) isExpr() {}
|
||||
func (*ENumber) isExpr() {}
|
||||
func (*EBigInt) isExpr() {}
|
||||
func (*EObject) isExpr() {}
|
||||
func (*ESpread) isExpr() {}
|
||||
func (*EString) isExpr() {}
|
||||
func (*ETemplate) isExpr() {}
|
||||
func (*ERegExp) isExpr() {}
|
||||
func (*EAwait) isExpr() {}
|
||||
func (*EYield) isExpr() {}
|
||||
func (*EIf) isExpr() {}
|
||||
func (*ERequire) isExpr() {}
|
||||
func (*EImport) isExpr() {}
|
||||
|
||||
type ExprOrStmt struct {
|
||||
Expr *Expr
|
||||
Stmt *Stmt
|
||||
}
|
||||
|
||||
type Stmt struct {
|
||||
Loc Loc
|
||||
Data S
|
||||
}
|
||||
|
||||
// This interface is never called. Its purpose is to encode a variant type in
|
||||
// Go's type system.
|
||||
type S interface{ isStmt() }
|
||||
|
||||
type SBlock struct {
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type SConst struct {
|
||||
Decls []Decl
|
||||
IsExport bool
|
||||
}
|
||||
|
||||
type SEmpty struct{}
|
||||
|
||||
type SDebugger struct{}
|
||||
|
||||
type SExportClause struct {
|
||||
Items []ClauseItem
|
||||
}
|
||||
|
||||
type SExportFrom struct {
|
||||
Items []ClauseItem
|
||||
NamespaceRef Ref
|
||||
Path Path
|
||||
}
|
||||
|
||||
type SExportDefault struct {
|
||||
DefaultName LocRef
|
||||
Value ExprOrStmt // May be a SFunction or SClass
|
||||
}
|
||||
|
||||
type SExportStar struct {
|
||||
Item *ClauseItem
|
||||
Path Path
|
||||
}
|
||||
|
||||
type SExpr struct {
|
||||
Value Expr
|
||||
}
|
||||
|
||||
type SFunction struct {
|
||||
Fn Fn
|
||||
IsExport bool
|
||||
}
|
||||
|
||||
type SClass struct {
|
||||
Class Class
|
||||
IsExport bool
|
||||
}
|
||||
|
||||
type SLabel struct {
|
||||
Name LocRef
|
||||
Stmt Stmt
|
||||
}
|
||||
|
||||
type SIf struct {
|
||||
Test Expr
|
||||
Yes Stmt
|
||||
No *Stmt
|
||||
}
|
||||
|
||||
type SFor struct {
|
||||
Init *Stmt // May be a SConst, SLet, SVar, or SExpr
|
||||
Test *Expr
|
||||
Update *Expr
|
||||
Body Stmt
|
||||
}
|
||||
|
||||
type SForIn struct {
|
||||
Init Stmt // May be a SConst, SLet, SVar, or SExpr
|
||||
Value Expr
|
||||
Body Stmt
|
||||
}
|
||||
|
||||
type SForOf struct {
|
||||
IsAwait bool
|
||||
Init Stmt // May be a SConst, SLet, SVar, or SExpr
|
||||
Value Expr
|
||||
Body Stmt
|
||||
}
|
||||
|
||||
type SDoWhile struct {
|
||||
Body Stmt
|
||||
Test Expr
|
||||
}
|
||||
|
||||
type SWhile struct {
|
||||
Test Expr
|
||||
Body Stmt
|
||||
}
|
||||
|
||||
type SWith struct {
|
||||
Value Expr
|
||||
Body Stmt
|
||||
}
|
||||
|
||||
type Catch struct {
|
||||
Binding *Binding
|
||||
Body []Stmt
|
||||
}
|
||||
|
||||
type STry struct {
|
||||
Body []Stmt
|
||||
Catch *Catch
|
||||
Finally *[]Stmt
|
||||
}
|
||||
|
||||
type Case struct {
|
||||
Value *Expr
|
||||
Body []Stmt
|
||||
}
|
||||
|
||||
type SSwitch struct {
|
||||
Test Expr
|
||||
Cases []Case
|
||||
}
|
||||
|
||||
// This object represents all of these types of import statements:
|
||||
//
|
||||
// import 'path'
|
||||
// import {item1, item2} from 'path'
|
||||
// import * as ns from 'path'
|
||||
// import defaultItem, {item1, item2} from 'path'
|
||||
// import defaultItem, * as ns from 'path'
|
||||
//
|
||||
// Many parts are optional and can be combined in different ways. The only
|
||||
// restriction is that you cannot have both a clause and a star namespace.
|
||||
type SImport struct {
|
||||
// If this is a star import: This is a Ref for the namespace symbol. The Loc
|
||||
// for the symbol is StarLoc.
|
||||
//
|
||||
// Otherwise: This is an auto-generated Ref for the namespace representing
|
||||
// the imported file. In this case StarLoc is nil. The NamespaceRef is used
|
||||
// when converting this module to a CommonJS module.
|
||||
NamespaceRef Ref
|
||||
|
||||
DefaultName *LocRef
|
||||
Items *[]ClauseItem
|
||||
StarLoc *Loc
|
||||
Path Path
|
||||
}
|
||||
|
||||
type SLet struct {
|
||||
Decls []Decl
|
||||
IsExport bool
|
||||
}
|
||||
|
||||
type SReturn struct {
|
||||
Value *Expr
|
||||
}
|
||||
|
||||
type SThrow struct {
|
||||
Value Expr
|
||||
}
|
||||
|
||||
type SVar struct {
|
||||
Decls []Decl
|
||||
IsExport bool
|
||||
}
|
||||
|
||||
type SBreak struct {
|
||||
Name *LocRef
|
||||
}
|
||||
|
||||
type SContinue struct {
|
||||
Name *LocRef
|
||||
}
|
||||
|
||||
func (*SBlock) isStmt() {}
|
||||
func (*SConst) isStmt() {}
|
||||
func (*SDebugger) isStmt() {}
|
||||
func (*SEmpty) isStmt() {}
|
||||
func (*SExportClause) isStmt() {}
|
||||
func (*SExportFrom) isStmt() {}
|
||||
func (*SExportDefault) isStmt() {}
|
||||
func (*SExportStar) isStmt() {}
|
||||
func (*SExpr) isStmt() {}
|
||||
func (*SFunction) isStmt() {}
|
||||
func (*SClass) isStmt() {}
|
||||
func (*SLabel) isStmt() {}
|
||||
func (*SIf) isStmt() {}
|
||||
func (*SFor) isStmt() {}
|
||||
func (*SForIn) isStmt() {}
|
||||
func (*SForOf) isStmt() {}
|
||||
func (*SDoWhile) isStmt() {}
|
||||
func (*SWhile) isStmt() {}
|
||||
func (*SWith) isStmt() {}
|
||||
func (*STry) isStmt() {}
|
||||
func (*SSwitch) isStmt() {}
|
||||
func (*SImport) isStmt() {}
|
||||
func (*SLet) isStmt() {}
|
||||
func (*SReturn) isStmt() {}
|
||||
func (*SThrow) isStmt() {}
|
||||
func (*SVar) isStmt() {}
|
||||
func (*SBreak) isStmt() {}
|
||||
func (*SContinue) isStmt() {}
|
||||
|
||||
type ClauseItem struct {
|
||||
Alias string
|
||||
AliasLoc Loc
|
||||
Name LocRef
|
||||
}
|
||||
|
||||
type Decl struct {
|
||||
Binding Binding
|
||||
Value *Expr
|
||||
}
|
||||
|
||||
type SymbolKind uint8
|
||||
|
||||
const (
|
||||
// An unbound symbol is one that isn't declared in the file it's referenced
|
||||
// in. For example, using "window" without declaring it will be unbound.
|
||||
SymbolUnbound SymbolKind = iota
|
||||
|
||||
// This has special merging behavior. You're allowed to re-declare these
|
||||
// symbols more than once in the same scope. These symbols are also hoisted
|
||||
// out of the scope they are declared in to the closest containing function
|
||||
// or module scope. These are the symbols with this kind:
|
||||
//
|
||||
// - Function arguments
|
||||
// - Function statements
|
||||
// - Variables declared using "var"
|
||||
//
|
||||
SymbolHoisted
|
||||
|
||||
// There's a weird special case where catch variables declared using a simple
|
||||
// identifier (i.e. not a binding pattern) block hoisted variables instead of
|
||||
// becoming an error:
|
||||
//
|
||||
// var e = 0;
|
||||
// try { throw 1 } catch (e) {
|
||||
// print(e) // 1
|
||||
// var e = 2
|
||||
// print(e) // 2
|
||||
// }
|
||||
// print(e) // 0 (since the hoisting stops at the catch block boundary)
|
||||
//
|
||||
// However, other forms are still a syntax error:
|
||||
//
|
||||
// try {} catch (e) { let e }
|
||||
// try {} catch ({e}) { var e }
|
||||
//
|
||||
// This symbol is for handling this weird special case.
|
||||
SymbolCatchIdentifier
|
||||
|
||||
// This annotates all other symbols that don't have special behavior.
|
||||
SymbolOther
|
||||
)
|
||||
|
||||
var InvalidRef Ref = Ref{^uint32(0), ^uint32(0)}
|
||||
|
||||
// Files are parsed in parallel for speed. We want to allow each parser to
|
||||
// generate symbol IDs that won't conflict with each other. We also want to be
|
||||
// able to quickly merge symbol tables from all files into one giant symbol
|
||||
// table.
|
||||
//
|
||||
// We can accomplish both goals by giving each symbol ID two parts: an outer
|
||||
// index that is unique to the parser goroutine, and an inner index that
|
||||
// increments as the parser generates new symbol IDs. Then a symbol map can
|
||||
// be an array of arrays indexed first by outer index, then by inner index.
|
||||
// The maps can be merged quickly by creating a single outer array containing
|
||||
// all inner arrays from all parsed files.
|
||||
type Ref struct {
|
||||
OuterIndex uint32
|
||||
InnerIndex uint32
|
||||
}
|
||||
|
||||
type Symbol struct {
|
||||
Kind SymbolKind
|
||||
|
||||
// An estimate of the number of uses of this symbol. This is used for
|
||||
// minification (to prefer shorter names for more frequently used symbols).
|
||||
// The reason why this is an estimate instead of an accurate count is that
|
||||
// it's not updated during dead code elimination for speed. I figure that
|
||||
// even without updating after parsing it's still a pretty good heuristic.
|
||||
UseCountEstimate uint32
|
||||
|
||||
Name string
|
||||
|
||||
// Used by the parser for single pass parsing. Symbols that have been merged
|
||||
// form a linked-list where the last link is the symbol to use. This link is
|
||||
// an invalid ref if it's the last link. If this isn't invalid, you need to
|
||||
// FollowSymbols to get the real one.
|
||||
Link Ref
|
||||
}
|
||||
|
||||
type ScopeKind int
|
||||
|
||||
const (
|
||||
ScopeBlock ScopeKind = iota
|
||||
ScopeLabel
|
||||
ScopeFunction
|
||||
ScopeFunctionName
|
||||
ScopeClassName
|
||||
ScopeModule
|
||||
)
|
||||
|
||||
type Scope struct {
|
||||
Kind ScopeKind
|
||||
Parent *Scope
|
||||
Children []*Scope
|
||||
Members map[string]Ref
|
||||
Generated []Ref
|
||||
|
||||
// This is the ref of the scope itself. This is currently only used to store
|
||||
// the ref of the label symbol for ScopeLabel scopes.
|
||||
ScopeRef Ref
|
||||
}
|
||||
|
||||
type SymbolMap struct {
|
||||
// This could be represented as a "map[Ref]Symbol" but a two-level array was
|
||||
// more efficient in profiles. This appears to be because it doesn't involve
|
||||
// a hash. This representation also makes it trivial to quickly merge symbol
|
||||
// maps from multiple files together. Each file only generates symbols in a
|
||||
// single inner array, so you can join the maps together by just make a
|
||||
// single outer array containing all of the inner arrays. See the comment on
|
||||
// "Ref" for more detail.
|
||||
Outer [][]Symbol
|
||||
}
|
||||
|
||||
func NewSymbolMap(maxSourceIndex int) *SymbolMap {
|
||||
return &SymbolMap{make([][]Symbol, maxSourceIndex+1)}
|
||||
}
|
||||
|
||||
func (sm *SymbolMap) Get(ref Ref) Symbol {
|
||||
return sm.Outer[ref.OuterIndex][ref.InnerIndex]
|
||||
}
|
||||
|
||||
func (sm *SymbolMap) IncrementUseCountEstimate(ref Ref) {
|
||||
sm.Outer[ref.OuterIndex][ref.InnerIndex].UseCountEstimate++
|
||||
}
|
||||
|
||||
// The symbol must already exist to call this
|
||||
func (sm *SymbolMap) Set(ref Ref, symbol Symbol) {
|
||||
sm.Outer[ref.OuterIndex][ref.InnerIndex] = symbol
|
||||
}
|
||||
|
||||
// The symbol may not already exist when you call this
|
||||
func (sm *SymbolMap) SetNew(ref Ref, symbol Symbol) {
|
||||
outer := sm.Outer
|
||||
inner := outer[ref.OuterIndex]
|
||||
innerLen := uint32(len(inner))
|
||||
if ref.InnerIndex >= innerLen {
|
||||
inner = append(inner, make([]Symbol, ref.InnerIndex+1-innerLen)...)
|
||||
outer[ref.OuterIndex] = inner
|
||||
}
|
||||
inner[ref.InnerIndex] = symbol
|
||||
}
|
||||
|
||||
type ImportKind uint8
|
||||
|
||||
const (
|
||||
ImportStmt ImportKind = iota
|
||||
ImportRequire
|
||||
ImportDynamic
|
||||
)
|
||||
|
||||
type ImportPath struct {
|
||||
Path Path
|
||||
Kind ImportKind
|
||||
}
|
||||
|
||||
type AST struct {
|
||||
ImportPaths []ImportPath
|
||||
|
||||
// ENamespaceImport items in this map are printed as an indirect access off
|
||||
// of the namespace. This is a way for the bundler to pass this information
|
||||
// to the printer. This is necessary when using a namespace import or when
|
||||
// an import item must be converted to a property access off a require() call.
|
||||
IndirectImportItems map[Ref]bool
|
||||
|
||||
// This is true if something used the "exports" or "module" variables, which
|
||||
// means they could have exported something. This is used to silence errors
|
||||
// about mismatched exports.
|
||||
HasCommonJsExports bool
|
||||
|
||||
Stmts []Stmt
|
||||
Symbols *SymbolMap
|
||||
ModuleScope *Scope
|
||||
ExportsRef Ref
|
||||
RequireRef Ref
|
||||
ModuleRef Ref
|
||||
}
|
||||
|
||||
// Returns the canonical ref that represents the ref for the provided symbol.
|
||||
// This may not be the provided ref if the symbol has been merged with another
|
||||
// symbol.
|
||||
func FollowSymbols(symbols *SymbolMap, ref Ref) Ref {
|
||||
symbol := symbols.Get(ref)
|
||||
if symbol.Link == InvalidRef {
|
||||
return ref
|
||||
}
|
||||
|
||||
link := FollowSymbols(symbols, symbol.Link)
|
||||
|
||||
// Only write if needed to avoid concurrent map update hazards
|
||||
if symbol.Link != link {
|
||||
symbol.Link = link
|
||||
symbols.Set(ref, symbol)
|
||||
}
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
// Use this before calling "FollowSymbols" from separate threads to avoid
|
||||
// concurrent map update hazards. In Go, mutating a map is not threadsafe
|
||||
// but reading from a map is. Calling "FollowAllSymbols" first ensures that
|
||||
// all mutation is done up front.
|
||||
func FollowAllSymbols(symbols *SymbolMap) {
|
||||
for sourceIndex, inner := range symbols.Outer {
|
||||
for symbolIndex, _ := range inner {
|
||||
FollowSymbols(symbols, Ref{uint32(sourceIndex), uint32(symbolIndex)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Makes "old" point to "new" by joining the linked lists for the two symbols
|
||||
// together. That way "FollowSymbols" on both "old" and "new" will result in
|
||||
// the same ref.
|
||||
func MergeSymbols(symbols *SymbolMap, old Ref, new Ref) Ref {
|
||||
if old == new {
|
||||
return new
|
||||
}
|
||||
|
||||
oldSymbol := symbols.Get(old)
|
||||
if oldSymbol.Link != InvalidRef {
|
||||
oldSymbol.Link = MergeSymbols(symbols, oldSymbol.Link, new)
|
||||
symbols.Set(old, oldSymbol)
|
||||
return oldSymbol.Link
|
||||
}
|
||||
|
||||
newSymbol := symbols.Get(new)
|
||||
if newSymbol.Link != InvalidRef {
|
||||
newSymbol.Link = MergeSymbols(symbols, old, newSymbol.Link)
|
||||
symbols.Set(new, newSymbol)
|
||||
return newSymbol.Link
|
||||
}
|
||||
|
||||
oldSymbol.Link = new
|
||||
newSymbol.UseCountEstimate += oldSymbol.UseCountEstimate
|
||||
symbols.Set(old, oldSymbol)
|
||||
symbols.Set(new, newSymbol)
|
||||
return new
|
||||
}
|
||||
|
||||
func GenerateNonUniqueNameFromPath(text string) string {
|
||||
// Get the file name without the extension
|
||||
base := path.Base(text)
|
||||
lastDot := strings.LastIndexByte(base, '.')
|
||||
if lastDot >= 0 {
|
||||
base = base[:lastDot]
|
||||
}
|
||||
|
||||
// Convert it to an ASCII identifier
|
||||
bytes := []byte{}
|
||||
for _, c := range base {
|
||||
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (len(bytes) > 0 && c >= '0' && c <= '9') {
|
||||
bytes = append(bytes, byte(c))
|
||||
} else if len(bytes) > 0 && bytes[len(bytes)-1] != '_' {
|
||||
bytes = append(bytes, '_')
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the name isn't empty
|
||||
if len(bytes) == 0 {
|
||||
return "_"
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,291 @@
|
||||
package bundler
|
||||
|
||||
import (
|
||||
"esbuild/ast"
|
||||
"esbuild/lexer"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func numberToMinifiedName(i int) string {
|
||||
j := i % 54
|
||||
name := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$"[j : j+1]
|
||||
i = i / 54
|
||||
|
||||
for i > 0 {
|
||||
i--
|
||||
j := i % 64
|
||||
name += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$0123456789"[j : j+1]
|
||||
i = i / 64
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func reservedNames(moduleScopes []*ast.Scope, symbols *ast.SymbolMap) map[string]bool {
|
||||
names := make(map[string]bool)
|
||||
|
||||
// All keywords are reserved names
|
||||
for k, _ := range lexer.Keywords() {
|
||||
names[k] = true
|
||||
}
|
||||
|
||||
// All unbound symbols must be reserved names
|
||||
for _, scope := range moduleScopes {
|
||||
for _, ref := range scope.Members {
|
||||
symbol := symbols.Get(ref)
|
||||
if symbol.Kind == ast.SymbolUnbound {
|
||||
names[symbol.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func sortedSymbolsInScope(scope *ast.Scope) uint64Array {
|
||||
// Sort for determinism
|
||||
sorted := uint64Array(make([]uint64, 0, len(scope.Members)+len(scope.Generated)))
|
||||
for _, ref := range scope.Members {
|
||||
sorted = append(sorted, (uint64(ref.OuterIndex)<<32)|uint64(ref.InnerIndex))
|
||||
}
|
||||
for _, ref := range scope.Generated {
|
||||
sorted = append(sorted, (uint64(ref.OuterIndex)<<32)|uint64(ref.InnerIndex))
|
||||
}
|
||||
sort.Sort(sorted)
|
||||
return sorted
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// renameAllSymbols() implementation
|
||||
|
||||
type renamer struct {
|
||||
parent *renamer
|
||||
names map[string]bool
|
||||
}
|
||||
|
||||
func (r *renamer) isNameUsed(name string) bool {
|
||||
for {
|
||||
if _, ok := r.names[name]; ok {
|
||||
return true
|
||||
}
|
||||
r = r.parent
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renamer) findUnusedName(prefix string) string {
|
||||
name := prefix
|
||||
tries := 1
|
||||
for {
|
||||
if !r.isNameUsed(name) {
|
||||
r.names[name] = true
|
||||
return name
|
||||
}
|
||||
tries++
|
||||
name = prefix + strconv.Itoa(tries)
|
||||
}
|
||||
}
|
||||
|
||||
func renameAllSymbols(moduleScopes []*ast.Scope, symbols *ast.SymbolMap) {
|
||||
r := &renamer{nil, reservedNames(moduleScopes, symbols)}
|
||||
alreadyRenamed := make(map[ast.Ref]bool)
|
||||
|
||||
// Rename top-level symbols across all files all at once since after
|
||||
// bundling, they will all be in the same scope
|
||||
for _, scope := range moduleScopes {
|
||||
r.renameSymbolsInScope(scope, symbols, alreadyRenamed)
|
||||
}
|
||||
|
||||
// Symbols in child scopes may also have to be renamed to avoid conflicts
|
||||
for _, scope := range moduleScopes {
|
||||
for _, child := range scope.Children {
|
||||
r.renameAllSymbolsRecursive(child, symbols, alreadyRenamed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renamer) renameSymbolsInScope(scope *ast.Scope, symbols *ast.SymbolMap, alreadyRenamed map[ast.Ref]bool) {
|
||||
sorted := sortedSymbolsInScope(scope)
|
||||
|
||||
// Rename all symbols in this scope
|
||||
for _, i := range sorted {
|
||||
ref := ast.Ref{uint32(i >> 32), uint32(i)}
|
||||
ref = ast.FollowSymbols(symbols, ref)
|
||||
|
||||
// Don't rename the same symbol more than once
|
||||
if alreadyRenamed[ref] {
|
||||
continue
|
||||
}
|
||||
alreadyRenamed[ref] = true
|
||||
|
||||
symbol := symbols.Get(ref)
|
||||
|
||||
// Don't rename unbound symbols
|
||||
if symbol.Kind == ast.SymbolUnbound {
|
||||
continue
|
||||
}
|
||||
|
||||
symbol.Name = r.findUnusedName(symbol.Name)
|
||||
symbols.Set(ref, symbol)
|
||||
}
|
||||
}
|
||||
|
||||
func (parent *renamer) renameAllSymbolsRecursive(scope *ast.Scope, symbols *ast.SymbolMap, alreadyRenamed map[ast.Ref]bool) {
|
||||
r := &renamer{parent, make(map[string]bool)}
|
||||
r.renameSymbolsInScope(scope, symbols, alreadyRenamed)
|
||||
|
||||
// Symbols in child scopes may also have to be renamed to avoid conflicts
|
||||
for _, child := range scope.Children {
|
||||
r.renameAllSymbolsRecursive(child, symbols, alreadyRenamed)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// minifyAllSymbols() implementation
|
||||
|
||||
func minifyAllSymbols(moduleScopes []*ast.Scope, symbols *ast.SymbolMap) {
|
||||
g := minifyGroup{[]uint32{}, make(map[ast.Ref]uint32)}
|
||||
var next uint32 = 0
|
||||
|
||||
// Allocate a slot for every symbol in each top-level scope. These slots must
|
||||
// not overlap between files because the bundler may smoosh everything
|
||||
// together into a single scope.
|
||||
for _, scope := range moduleScopes {
|
||||
next = g.countSymbolsInScope(scope, symbols, next)
|
||||
}
|
||||
|
||||
// Allocate a slot for every symbol in each nested scope. Since it's
|
||||
// impossible for symbols from nested scopes to conflict, symbols from
|
||||
// different nested scopes can reuse the same slots (and therefore get the
|
||||
// same minified names).
|
||||
//
|
||||
// One good heuristic is to merge slots from different nested scopes using
|
||||
// sequential assignment. Then top-level function statements will always have
|
||||
// the same argument names, which is better for gzip compression.
|
||||
for _, scope := range moduleScopes {
|
||||
for _, child := range scope.Children {
|
||||
// Deliberately don't update "next" here. Sibling scopes can't collide and
|
||||
// so can reuse slots.
|
||||
g.countSymbolsRecursive(child, symbols, next, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort slot indices descending by the count for that slot
|
||||
sorted := slotArray(make([]slot, len(g.slotToCount)))
|
||||
for index, count := range g.slotToCount {
|
||||
sorted[index] = slot{count, uint32(index)}
|
||||
}
|
||||
sort.Sort(sorted)
|
||||
|
||||
reservedNames := reservedNames(moduleScopes, symbols)
|
||||
names := make([]string, len(sorted))
|
||||
nextName := 0
|
||||
|
||||
// Assign names sequentially in order so the most frequent symbols get the
|
||||
// shortest names
|
||||
for _, slot := range sorted {
|
||||
name := numberToMinifiedName(nextName)
|
||||
nextName++
|
||||
|
||||
// Make sure we never generate a reserved name
|
||||
for reservedNames[name] {
|
||||
name = numberToMinifiedName(nextName)
|
||||
nextName++
|
||||
}
|
||||
|
||||
names[slot.index] = name
|
||||
}
|
||||
|
||||
// Copy the names to the appropriate symbols
|
||||
for ref, i := range g.symbolToSlot {
|
||||
symbol := symbols.Get(ref)
|
||||
symbol.Name = names[i]
|
||||
symbols.Set(ref, symbol)
|
||||
}
|
||||
}
|
||||
|
||||
type minifyGroup struct {
|
||||
slotToCount []uint32
|
||||
symbolToSlot map[ast.Ref]uint32
|
||||
}
|
||||
|
||||
func (g *minifyGroup) countSymbol(slot uint32, ref ast.Ref, count uint32) bool {
|
||||
// Don't double-count symbols that have already been counted
|
||||
if _, ok := g.symbolToSlot[ref]; ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Optionally extend the slot array
|
||||
if slot == uint32(len(g.slotToCount)) {
|
||||
g.slotToCount = append(g.slotToCount, 0)
|
||||
}
|
||||
|
||||
// Count this symbol in this slot
|
||||
g.slotToCount[slot] += count
|
||||
g.symbolToSlot[ref] = slot
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *minifyGroup) countSymbolsInScope(scope *ast.Scope, symbols *ast.SymbolMap, next uint32) uint32 {
|
||||
sorted := sortedSymbolsInScope(scope)
|
||||
|
||||
for _, i := range sorted {
|
||||
ref := ast.Ref{uint32(i >> 32), uint32(i)}
|
||||
ref = ast.FollowSymbols(symbols, ref)
|
||||
|
||||
symbol := symbols.Get(ref)
|
||||
|
||||
// Only count bound symbols
|
||||
if symbol.Kind == ast.SymbolUnbound {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add 1 to the count to also include the declaration
|
||||
if g.countSymbol(next, ref, symbol.UseCountEstimate+1) {
|
||||
next += 1
|
||||
}
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
func (g *minifyGroup) countSymbolsRecursive(scope *ast.Scope, symbols *ast.SymbolMap, next uint32, labelCount uint32) uint32 {
|
||||
next = g.countSymbolsInScope(scope, symbols, next)
|
||||
|
||||
// Labels are in a separate namespace from symbols
|
||||
if scope.Kind == ast.ScopeLabel {
|
||||
symbol := symbols.Get(scope.ScopeRef)
|
||||
g.countSymbol(labelCount, scope.ScopeRef, symbol.UseCountEstimate+1)
|
||||
labelCount += 1
|
||||
}
|
||||
|
||||
for _, child := range scope.Children {
|
||||
// Deliberately don't update "next" here. Sibling scopes can't collide and
|
||||
// so can reuse slots.
|
||||
g.countSymbolsRecursive(child, symbols, next, labelCount)
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
type slot struct {
|
||||
count uint32
|
||||
index uint32
|
||||
}
|
||||
|
||||
// These types are just so we can use Go's native sort function
|
||||
type uint64Array []uint64
|
||||
type slotArray []slot
|
||||
|
||||
func (a uint64Array) Len() int { return len(a) }
|
||||
func (a uint64Array) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a uint64Array) Less(i int, j int) bool { return a[i] < a[j] }
|
||||
|
||||
func (a slotArray) Len() int { return len(a) }
|
||||
func (a slotArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a slotArray) Less(i int, j int) bool {
|
||||
ai, aj := a[i], a[j]
|
||||
return ai.count > aj.count || (ai.count == aj.count && ai.index < aj.index)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,521 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"esbuild/logging"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func assertEqual(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Fatalf("%s != %s", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqualStrings(t *testing.T, a string, b string) {
|
||||
pretty := func(text string) string {
|
||||
builder := strings.Builder{}
|
||||
builder.WriteRune('"')
|
||||
i := 0
|
||||
for i < len(text) {
|
||||
c, width := utf8.DecodeRuneInString(text[i:])
|
||||
builder.WriteString(fmt.Sprintf("\\u{%X}", c))
|
||||
i += width
|
||||
}
|
||||
builder.WriteRune('"')
|
||||
return builder.String()
|
||||
}
|
||||
if a != b {
|
||||
t.Fatalf("%s != %s", pretty(a), pretty(b))
|
||||
}
|
||||
}
|
||||
|
||||
func lexToken(t *testing.T, contents string) T {
|
||||
log, join := logging.NewDeferLog()
|
||||
lexer := NewLexer(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
})
|
||||
join()
|
||||
return lexer.Token
|
||||
}
|
||||
|
||||
func expectLexerError(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if _, isLexerPanic := r.(LexerPanic); r != nil && !isLexerPanic {
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
NewLexer(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
})
|
||||
}()
|
||||
msgs := join()
|
||||
text := ""
|
||||
for _, msg := range msgs {
|
||||
text += msg.String(logging.StderrOptions{}, logging.TerminalInfo{})
|
||||
}
|
||||
assertEqual(t, text, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func expectIdentifier(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
lexer := func() Lexer {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if _, isLexerPanic := r.(LexerPanic); r != nil && !isLexerPanic {
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
return NewLexer(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
})
|
||||
}()
|
||||
msgs := join()
|
||||
assertEqual(t, len(msgs), 0)
|
||||
assertEqual(t, lexer.Token, TIdentifier)
|
||||
assertEqual(t, lexer.Identifier, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIdentifier(t *testing.T) {
|
||||
expectIdentifier(t, "_", "_")
|
||||
expectIdentifier(t, "$", "$")
|
||||
expectIdentifier(t, "test", "test")
|
||||
expectIdentifier(t, "t\\u0065st", "test")
|
||||
expectIdentifier(t, "t\\u{65}st", "test")
|
||||
|
||||
expectLexerError(t, "t\\u.", "<stdin>: error: Syntax error \".\"\n")
|
||||
expectLexerError(t, "t\\u0.", "<stdin>: error: Syntax error \".\"\n")
|
||||
expectLexerError(t, "t\\u00.", "<stdin>: error: Syntax error \".\"\n")
|
||||
expectLexerError(t, "t\\u006.", "<stdin>: error: Syntax error \".\"\n")
|
||||
expectLexerError(t, "t\\u{.", "<stdin>: error: Syntax error \".\"\n")
|
||||
expectLexerError(t, "t\\u{0.", "<stdin>: error: Syntax error \".\"\n")
|
||||
}
|
||||
|
||||
func expectNumber(t *testing.T, contents string, expected float64) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
lexer := func() Lexer {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if _, isLexerPanic := r.(LexerPanic); r != nil && !isLexerPanic {
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
return NewLexer(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
})
|
||||
}()
|
||||
msgs := join()
|
||||
assertEqual(t, len(msgs), 0)
|
||||
assertEqual(t, lexer.Token, TNumericLiteral)
|
||||
assertEqual(t, lexer.Number, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNumericLiteral(t *testing.T) {
|
||||
expectNumber(t, "0", 0.0)
|
||||
expectNumber(t, "000", 0.0)
|
||||
expectNumber(t, "010", 8.0)
|
||||
expectNumber(t, "123", 123.0)
|
||||
expectNumber(t, "987", 987.0)
|
||||
expectNumber(t, "0000", 0.0)
|
||||
expectNumber(t, "0123", 83.0)
|
||||
expectNumber(t, "0123.4567", 83.0)
|
||||
expectNumber(t, "0987", 987.0)
|
||||
expectNumber(t, "0987.6543", 987.6543)
|
||||
expectNumber(t, "01289", 1289.0)
|
||||
expectNumber(t, "01289.345", 1289.0)
|
||||
expectNumber(t, "999999999", 999999999.0)
|
||||
expectNumber(t, "9999999999", 9999999999.0)
|
||||
expectNumber(t, "99999999999", 99999999999.0)
|
||||
expectNumber(t, "123456789123456789", 123456789123456780.0)
|
||||
expectNumber(t, "123456789123456789"+strings.Repeat("0", 128), 1.2345678912345679e+145)
|
||||
|
||||
expectNumber(t, "0b00101", 5.0)
|
||||
expectNumber(t, "0B00101", 5.0)
|
||||
expectNumber(t, "0b1011101011101011101011101011101011101", 100352251741.0)
|
||||
expectNumber(t, "0B1011101011101011101011101011101011101", 100352251741.0)
|
||||
expectLexerError(t, "0b", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "0B", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "0b012", "<stdin>: error: Syntax error \"2\"\n")
|
||||
expectLexerError(t, "0b018", "<stdin>: error: Syntax error \"8\"\n")
|
||||
expectLexerError(t, "0b01a", "<stdin>: error: Syntax error \"a\"\n")
|
||||
expectLexerError(t, "0b01A", "<stdin>: error: Syntax error \"A\"\n")
|
||||
|
||||
expectNumber(t, "0o12345", 5349.0)
|
||||
expectNumber(t, "0O12345", 5349.0)
|
||||
expectNumber(t, "0o1234567654321", 89755965649.0)
|
||||
expectNumber(t, "0O1234567654321", 89755965649.0)
|
||||
expectLexerError(t, "0o", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "0O", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "0o018", "<stdin>: error: Syntax error \"8\"\n")
|
||||
expectLexerError(t, "0o01a", "<stdin>: error: Syntax error \"a\"\n")
|
||||
expectLexerError(t, "0o01A", "<stdin>: error: Syntax error \"A\"\n")
|
||||
|
||||
expectNumber(t, "0x12345678", float64(0x12345678))
|
||||
expectNumber(t, "0xFEDCBA987", float64(0xFEDCBA987))
|
||||
expectNumber(t, "0x000012345678", float64(0x12345678))
|
||||
expectNumber(t, "0x123456781234", float64(0x123456781234))
|
||||
expectLexerError(t, "0x", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "0X", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "0xGFEDCBA", "<stdin>: error: Syntax error \"G\"\n")
|
||||
expectLexerError(t, "0xABCDEFG", "<stdin>: error: Syntax error \"G\"\n")
|
||||
|
||||
expectNumber(t, "123.", 123.0)
|
||||
expectNumber(t, ".0123", 0.0123)
|
||||
expectNumber(t, "0.0123", 0.0123)
|
||||
expectNumber(t, "2.2250738585072014e-308", 2.2250738585072014e-308)
|
||||
expectNumber(t, "1.7976931348623157e+308", 1.7976931348623157e+308)
|
||||
|
||||
// Underflow
|
||||
expectNumber(t, "4.9406564584124654417656879286822e-324", 5e-324)
|
||||
expectNumber(t, "5e-324", 5e-324)
|
||||
expectNumber(t, "1e-325", 0.0)
|
||||
|
||||
// Overflow
|
||||
expectNumber(t, "1.797693134862315708145274237317e+308", 1.7976931348623157e+308)
|
||||
expectNumber(t, "1.797693134862315808e+308", math.Inf(1))
|
||||
expectNumber(t, "1e+309", math.Inf(1))
|
||||
|
||||
expectNumber(t, "1.", 1.0)
|
||||
expectNumber(t, ".1", 0.1)
|
||||
expectNumber(t, "1.1", 1.1)
|
||||
expectNumber(t, "1e1", 10.0)
|
||||
expectNumber(t, "1e+1", 10.0)
|
||||
expectNumber(t, "1e-1", 0.1)
|
||||
expectNumber(t, ".1e1", 1.0)
|
||||
expectNumber(t, ".1e+1", 1.0)
|
||||
expectNumber(t, ".1e-1", 0.01)
|
||||
expectNumber(t, "1.e1", 10.0)
|
||||
expectNumber(t, "1.e+1", 10.0)
|
||||
expectNumber(t, "1.e-1", 0.1)
|
||||
expectNumber(t, "1.1e1", 11.0)
|
||||
expectNumber(t, "1.1e+1", 11.0)
|
||||
expectNumber(t, "1.1e-1", 0.11)
|
||||
|
||||
expectLexerError(t, "1e", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, ".1e", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1.e", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1.1e", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1e+", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, ".1e+", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1.e+", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1.1e+", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1e-", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, ".1e-", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1.e-", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1.1e-", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "1e+-1", "<stdin>: error: Syntax error \"-\"\n")
|
||||
expectLexerError(t, "1e-+1", "<stdin>: error: Syntax error \"+\"\n")
|
||||
|
||||
expectLexerError(t, "1z", "<stdin>: error: Syntax error \"z\"\n")
|
||||
expectLexerError(t, "1.z", "<stdin>: error: Syntax error \"z\"\n")
|
||||
expectLexerError(t, "1.0f", "<stdin>: error: Syntax error \"f\"\n")
|
||||
expectLexerError(t, "0b1z", "<stdin>: error: Syntax error \"z\"\n")
|
||||
expectLexerError(t, "0o1z", "<stdin>: error: Syntax error \"z\"\n")
|
||||
expectLexerError(t, "0x1z", "<stdin>: error: Syntax error \"z\"\n")
|
||||
expectLexerError(t, "1e1z", "<stdin>: error: Syntax error \"z\"\n")
|
||||
|
||||
expectNumber(t, "1_2_3", 123)
|
||||
expectNumber(t, ".1_2", 0.12)
|
||||
expectNumber(t, "1_2.3_4", 12.34)
|
||||
expectNumber(t, "1e2_3", 1e23)
|
||||
expectNumber(t, "1_2e3_4", 12e34)
|
||||
expectNumber(t, "1_2.3_4e5_6", 12.34e56)
|
||||
expectNumber(t, "0b1_0", 2)
|
||||
expectNumber(t, "0B1_0", 2)
|
||||
expectNumber(t, "0o1_2", 10)
|
||||
expectNumber(t, "0O1_2", 10)
|
||||
expectNumber(t, "0x1_2", 0x12)
|
||||
expectNumber(t, "0X1_2", 0x12)
|
||||
|
||||
expectLexerError(t, "1__2", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, ".1__2", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "1e2__3", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0b1__0", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0B1__0", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0o1__2", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0O1__2", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0x1__2", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0X1__2", "<stdin>: error: Syntax error \"_\"\n")
|
||||
|
||||
expectLexerError(t, "1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "1._", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, ".1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "1e_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "1e1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "1_e1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, ".1_e1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0b_1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0B_1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0o_1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0O_1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0x_1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0X_1", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0b1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0B1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0o1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0O1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0x1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
expectLexerError(t, "0X1_", "<stdin>: error: Syntax error \"_\"\n")
|
||||
}
|
||||
|
||||
func expectBigInteger(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
lexer := func() Lexer {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if _, isLexerPanic := r.(LexerPanic); r != nil && !isLexerPanic {
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
return NewLexer(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
})
|
||||
}()
|
||||
msgs := join()
|
||||
assertEqual(t, len(msgs), 0)
|
||||
assertEqual(t, lexer.Token, TBigIntegerLiteral)
|
||||
assertEqual(t, lexer.Identifier, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBigIntegerLiteral(t *testing.T) {
|
||||
expectBigInteger(t, "0n", "0")
|
||||
expectBigInteger(t, "123n", "123")
|
||||
expectBigInteger(t, "9007199254740993n", "9007199254740993") // This can't fit in a float64
|
||||
|
||||
expectBigInteger(t, "0b00101n", "0b00101")
|
||||
expectBigInteger(t, "0B00101n", "0B00101")
|
||||
expectBigInteger(t, "0b1011101011101011101011101011101011101n", "0b1011101011101011101011101011101011101")
|
||||
expectBigInteger(t, "0B1011101011101011101011101011101011101n", "0B1011101011101011101011101011101011101")
|
||||
|
||||
expectBigInteger(t, "0o12345n", "0o12345")
|
||||
expectBigInteger(t, "0O12345n", "0O12345")
|
||||
expectBigInteger(t, "0o1234567654321n", "0o1234567654321")
|
||||
expectBigInteger(t, "0O1234567654321n", "0O1234567654321")
|
||||
|
||||
expectBigInteger(t, "0x12345678n", "0x12345678")
|
||||
expectBigInteger(t, "0xFEDCBA987n", "0xFEDCBA987")
|
||||
expectBigInteger(t, "0x000012345678n", "0x000012345678")
|
||||
expectBigInteger(t, "0x123456781234n", "0x123456781234")
|
||||
|
||||
expectBigInteger(t, "1_2_3n", "123")
|
||||
expectBigInteger(t, "0b1_0_1n", "0b101")
|
||||
expectBigInteger(t, "0o1_2_3n", "0o123")
|
||||
expectBigInteger(t, "0x1_2_3n", "0x123")
|
||||
|
||||
expectLexerError(t, "1e2n", "<stdin>: error: Syntax error \"n\"\n")
|
||||
expectLexerError(t, "1.0n", "<stdin>: error: Syntax error \"n\"\n")
|
||||
expectLexerError(t, ".1n", "<stdin>: error: Syntax error \"n\"\n")
|
||||
expectLexerError(t, "000n", "<stdin>: error: Syntax error \"n\"\n")
|
||||
expectLexerError(t, "0123n", "<stdin>: error: Syntax error \"n\"\n")
|
||||
}
|
||||
|
||||
func expectString(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
lexer := func() Lexer {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if _, isLexerPanic := r.(LexerPanic); r != nil && !isLexerPanic {
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
return NewLexer(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
})
|
||||
}()
|
||||
msgs := join()
|
||||
assertEqual(t, len(msgs), 0)
|
||||
assertEqual(t, lexer.Token, TStringLiteral)
|
||||
assertEqualStrings(t, UTF16ToString(lexer.StringLiteral), expected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStringLiteral(t *testing.T) {
|
||||
expectString(t, "''", "")
|
||||
expectString(t, "'123'", "123")
|
||||
|
||||
expectString(t, "'\"'", "\"")
|
||||
expectString(t, "'\\''", "'")
|
||||
expectString(t, "'\\\"'", "\"")
|
||||
expectString(t, "'\\\\'", "\\")
|
||||
expectString(t, "'\\a'", "a")
|
||||
expectString(t, "'\\b'", "\b")
|
||||
expectString(t, "'\\f'", "\f")
|
||||
expectString(t, "'\\n'", "\n")
|
||||
expectString(t, "'\\r'", "\r")
|
||||
expectString(t, "'\\t'", "\t")
|
||||
expectString(t, "'\\v'", "\v")
|
||||
|
||||
expectString(t, "'\\0'", "\000")
|
||||
expectString(t, "'\\1'", "\001")
|
||||
expectString(t, "'\\2'", "\002")
|
||||
expectString(t, "'\\3'", "\003")
|
||||
expectString(t, "'\\4'", "\004")
|
||||
expectString(t, "'\\5'", "\005")
|
||||
expectString(t, "'\\6'", "\006")
|
||||
expectString(t, "'\\7'", "\007")
|
||||
|
||||
expectString(t, "'\\000'", "\000")
|
||||
expectString(t, "'\\001'", "\001")
|
||||
expectString(t, "'\\002'", "\002")
|
||||
expectString(t, "'\\003'", "\003")
|
||||
expectString(t, "'\\004'", "\004")
|
||||
expectString(t, "'\\005'", "\005")
|
||||
expectString(t, "'\\006'", "\006")
|
||||
expectString(t, "'\\007'", "\007")
|
||||
|
||||
expectString(t, "'\\000'", "\000")
|
||||
expectString(t, "'\\100'", "\100")
|
||||
expectString(t, "'\\200'", "\u0080")
|
||||
expectString(t, "'\\300'", "\u00C0")
|
||||
expectString(t, "'\\377'", "\u00FF")
|
||||
expectString(t, "'\\378'", "\0378")
|
||||
expectString(t, "'\\400'", "\0400")
|
||||
expectString(t, "'\\500'", "\0500")
|
||||
expectString(t, "'\\600'", "\0600")
|
||||
expectString(t, "'\\700'", "\0700")
|
||||
|
||||
expectString(t, "'\\x00'", "\x00")
|
||||
expectString(t, "'\\X11'", "X11")
|
||||
expectString(t, "'\\x71'", "\x71")
|
||||
expectString(t, "'\\x7f'", "\x7f")
|
||||
expectString(t, "'\\x7F'", "\x7F")
|
||||
|
||||
expectString(t, "'\\u0000'", "\u0000")
|
||||
expectString(t, "'\\ucafe\\uCAFE\\u7FFF'", "\ucafe\uCAFE\u7FFF")
|
||||
expectString(t, "'\\uD800'", "\xED\xA0\x80")
|
||||
expectString(t, "'\\uDC00'", "\xED\xB0\x80")
|
||||
expectString(t, "'\\U0000'", "U0000")
|
||||
|
||||
expectString(t, "'\\u{100000}'", "\U00100000")
|
||||
expectString(t, "'\\u{10FFFF}'", "\U0010FFFF")
|
||||
expectLexerError(t, "'\\u{110000}'", "<stdin>: error: Unicode escape sequence is out of range\n")
|
||||
expectLexerError(t, "'\\u{FFFFFFFF}'", "<stdin>: error: Unicode escape sequence is out of range\n")
|
||||
|
||||
// Line continuation
|
||||
expectString(t, "'1\\\n2'", "12")
|
||||
|
||||
expectLexerError(t, "\"'", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "'\"", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "'\\", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "'\\'", "<stdin>: error: Unexpected end of file\n")
|
||||
|
||||
expectLexerError(t, "'\\x", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "'\\x'", "<stdin>: error: Syntax error \"'\"\n")
|
||||
expectLexerError(t, "'\\xG'", "<stdin>: error: Syntax error \"G\"\n")
|
||||
expectLexerError(t, "'\\xF'", "<stdin>: error: Syntax error \"'\"\n")
|
||||
expectLexerError(t, "'\\xFG'", "<stdin>: error: Syntax error \"G\"\n")
|
||||
|
||||
expectLexerError(t, "'\\u", "<stdin>: error: Unexpected end of file\n")
|
||||
expectLexerError(t, "'\\u'", "<stdin>: error: Syntax error \"'\"\n")
|
||||
expectLexerError(t, "'\\u0'", "<stdin>: error: Syntax error \"'\"\n")
|
||||
expectLexerError(t, "'\\u00'", "<stdin>: error: Syntax error \"'\"\n")
|
||||
expectLexerError(t, "'\\u000'", "<stdin>: error: Syntax error \"'\"\n")
|
||||
}
|
||||
|
||||
func TestTokens(t *testing.T) {
|
||||
expected := []struct {
|
||||
contents string
|
||||
token T
|
||||
}{
|
||||
{"", TEndOfFile},
|
||||
{"\x00", TSyntaxError},
|
||||
|
||||
// Punctuation
|
||||
{"(", TOpenParen},
|
||||
{")", TCloseParen},
|
||||
{"[", TOpenBracket},
|
||||
{"]", TCloseBracket},
|
||||
{"{", TOpenBrace},
|
||||
{"}", TCloseBrace},
|
||||
|
||||
// Reserved words
|
||||
{"break", TBreak},
|
||||
{"case", TCase},
|
||||
{"catch", TCatch},
|
||||
{"class", TClass},
|
||||
{"const", TConst},
|
||||
{"continue", TContinue},
|
||||
{"debugger", TDebugger},
|
||||
{"default", TDefault},
|
||||
{"delete", TDelete},
|
||||
{"do", TDo},
|
||||
{"else", TElse},
|
||||
{"enum", TEnum},
|
||||
{"export", TExport},
|
||||
{"extends", TExtends},
|
||||
{"false", TFalse},
|
||||
{"finally", TFinally},
|
||||
{"for", TFor},
|
||||
{"function", TFunction},
|
||||
{"if", TIf},
|
||||
{"import", TImport},
|
||||
{"in", TIn},
|
||||
{"instanceof", TInstanceOf},
|
||||
{"new", TNew},
|
||||
{"null", TNull},
|
||||
{"return", TReturn},
|
||||
{"super", TSuper},
|
||||
{"switch", TSwitch},
|
||||
{"this", TThis},
|
||||
{"throw", TThrow},
|
||||
{"true", TTrue},
|
||||
{"try", TTry},
|
||||
{"typeof", TTypeOf},
|
||||
{"var", TVar},
|
||||
{"void", TVoid},
|
||||
{"while", TWhile},
|
||||
{"with", TWith},
|
||||
|
||||
// Strict mode reserved words
|
||||
{"implements", TImplements},
|
||||
{"interface", TInterface},
|
||||
{"let", TLet},
|
||||
{"package", TPackage},
|
||||
{"private", TPrivate},
|
||||
{"protected", TProtected},
|
||||
{"public", TPublic},
|
||||
{"static", TStatic},
|
||||
{"yield", TYield},
|
||||
}
|
||||
|
||||
for _, it := range expected {
|
||||
contents := it.contents
|
||||
token := it.token
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
assertEqual(t, lexToken(t, contents), token)
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,453 @@
|
||||
package logging
|
||||
|
||||
// Logging is currently designed to look and feel like clang's error format.
|
||||
// Errors are streamed asynchronously as they happen, each error contains the
|
||||
// contents of the line with the error, and the error count is limited by
|
||||
// default.
|
||||
|
||||
import (
|
||||
"esbuild/ast"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Log struct {
|
||||
msgs chan Msg
|
||||
}
|
||||
|
||||
type MsgKind int
|
||||
|
||||
const (
|
||||
Error MsgKind = iota
|
||||
Warning
|
||||
)
|
||||
|
||||
type Msg struct {
|
||||
source Source
|
||||
start int32
|
||||
length int32
|
||||
text string
|
||||
kind MsgKind
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
Index uint32
|
||||
AbsolutePath string
|
||||
PrettyPath string
|
||||
Contents string
|
||||
}
|
||||
|
||||
func (s *Source) TextForRange(r ast.Range) string {
|
||||
return s.Contents[r.Loc.Start : r.Loc.Start+r.Len]
|
||||
}
|
||||
|
||||
func (s *Source) RangeOfString(loc ast.Loc) ast.Range {
|
||||
text := s.Contents[loc.Start:]
|
||||
if len(text) == 0 {
|
||||
return ast.Range{loc, 0}
|
||||
}
|
||||
|
||||
quote := text[0]
|
||||
if quote == '"' || quote == '\'' {
|
||||
// Search for the matching quote character
|
||||
for i := 1; i < len(text); i++ {
|
||||
c := text[i]
|
||||
if c == quote {
|
||||
return ast.Range{loc, int32(i + 1)}
|
||||
} else if c == '\\' {
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ast.Range{loc, 0}
|
||||
}
|
||||
|
||||
func NewLog(msgs chan Msg) Log {
|
||||
return Log{msgs}
|
||||
}
|
||||
|
||||
type MsgCounts struct {
|
||||
Errors int
|
||||
Warnings int
|
||||
}
|
||||
|
||||
func plural(prefix string, count int) string {
|
||||
if count == 1 {
|
||||
return fmt.Sprintf("%d %s", count, prefix)
|
||||
}
|
||||
return fmt.Sprintf("%d %ss", count, prefix)
|
||||
}
|
||||
|
||||
func (counts MsgCounts) String() string {
|
||||
if counts.Errors == 0 {
|
||||
if counts.Warnings == 0 {
|
||||
return "no errors"
|
||||
} else {
|
||||
return plural("warning", counts.Warnings)
|
||||
}
|
||||
} else {
|
||||
if counts.Warnings == 0 {
|
||||
return plural("error", counts.Errors)
|
||||
} else {
|
||||
return fmt.Sprintf("%s and %s",
|
||||
plural("warning", counts.Warnings),
|
||||
plural("error", counts.Errors))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TerminalInfo struct {
|
||||
UseColor bool
|
||||
Width int
|
||||
}
|
||||
|
||||
func StdinTerminalInfo() TerminalInfo {
|
||||
// This approach doesn't work on Windows. Just don't use color on Windows.
|
||||
if runtime.GOOS == "windows" {
|
||||
return TerminalInfo{}
|
||||
}
|
||||
|
||||
// This approach works on macOS and Linux without using syscalls that are
|
||||
// hard to cross-compile. Worst case we just don't use color, which isn't bad.
|
||||
cmd := exec.Command("stty", "size")
|
||||
cmd.Stdin = os.Stdin
|
||||
if out, err := cmd.Output(); err == nil {
|
||||
if space := strings.IndexByte(string(out), ' '); space != -1 {
|
||||
if newline := strings.IndexByte(string(out), '\n'); newline > space {
|
||||
if width, err := strconv.Atoi(string(out[space+1 : newline])); err == nil && width > 0 {
|
||||
return TerminalInfo{true, width}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TerminalInfo{}
|
||||
}
|
||||
|
||||
func asyncStdinTerminalInfo() func() TerminalInfo {
|
||||
channel := make(chan *TerminalInfo)
|
||||
var result *TerminalInfo
|
||||
|
||||
// Run the command asynchronously
|
||||
go func() {
|
||||
info := StdinTerminalInfo()
|
||||
channel <- &info
|
||||
}()
|
||||
|
||||
// Return a function that blocks on the result of the asynchronous command
|
||||
return func() TerminalInfo {
|
||||
if result == nil {
|
||||
result = <-channel
|
||||
}
|
||||
return *result
|
||||
}
|
||||
}
|
||||
|
||||
func NewStderrLog(options StderrOptions) (Log, func() MsgCounts) {
|
||||
msgs := make(chan Msg)
|
||||
done := make(chan MsgCounts)
|
||||
log := NewLog(msgs)
|
||||
|
||||
// Get the terminal info asynchronously to avoid blocking this thread
|
||||
asyncTerminalInfo := asyncStdinTerminalInfo()
|
||||
|
||||
go func(msgs chan Msg, done chan MsgCounts) {
|
||||
counts := MsgCounts{}
|
||||
for msg := range msgs {
|
||||
os.Stderr.WriteString(msg.String(options, asyncTerminalInfo()))
|
||||
switch msg.kind {
|
||||
case Error:
|
||||
counts.Errors++
|
||||
case Warning:
|
||||
counts.Warnings++
|
||||
}
|
||||
if options.ExitWhenLimitIsHit && options.ErrorLimit != 0 && counts.Errors >= options.ErrorLimit {
|
||||
fmt.Fprintf(os.Stderr, "%s reached (disable error limit with --error-limit=0)\n", counts.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
done <- counts
|
||||
}(msgs, done)
|
||||
|
||||
return log, func() MsgCounts {
|
||||
close(log.msgs)
|
||||
counts := <-done
|
||||
if counts.Warnings != 0 || counts.Errors != 0 {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", counts.String())
|
||||
}
|
||||
return counts
|
||||
}
|
||||
}
|
||||
|
||||
func NewDeferLog() (Log, func() []Msg) {
|
||||
msgs := make(chan Msg)
|
||||
done := make(chan []Msg)
|
||||
log := NewLog(msgs)
|
||||
|
||||
go func(msgs chan Msg, done chan []Msg) {
|
||||
result := []Msg{}
|
||||
for msg := range msgs {
|
||||
result = append(result, msg)
|
||||
}
|
||||
done <- result
|
||||
}(msgs, done)
|
||||
|
||||
return log, func() []Msg {
|
||||
close(log.msgs)
|
||||
return <-done
|
||||
}
|
||||
}
|
||||
|
||||
const colorReset = "\033[0m"
|
||||
const colorRed = "\033[31m"
|
||||
const colorGreen = "\033[32m"
|
||||
const colorMagenta = "\033[35m"
|
||||
const colorBold = "\033[1m"
|
||||
const colorResetBold = "\033[0;1m"
|
||||
|
||||
type StderrOptions struct {
|
||||
IncludeSource bool
|
||||
ErrorLimit int
|
||||
ExitWhenLimitIsHit bool
|
||||
}
|
||||
|
||||
func (msg Msg) String(options StderrOptions, terminalInfo TerminalInfo) string {
|
||||
kind := "error"
|
||||
kindColor := colorRed
|
||||
|
||||
if msg.kind == Warning {
|
||||
kind = "warning"
|
||||
kindColor = colorMagenta
|
||||
}
|
||||
|
||||
if msg.source.PrettyPath == "" {
|
||||
if terminalInfo.UseColor {
|
||||
return fmt.Sprintf("%s%s%s: %s%s%s\n",
|
||||
colorBold, kindColor, kind,
|
||||
colorResetBold, msg.text,
|
||||
colorReset)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: %s\n", kind, msg.text)
|
||||
}
|
||||
|
||||
if !options.IncludeSource {
|
||||
if terminalInfo.UseColor {
|
||||
return fmt.Sprintf("%s%s: %s%s: %s%s%s\n",
|
||||
colorBold, msg.source.PrettyPath,
|
||||
kindColor, kind,
|
||||
colorResetBold, msg.text,
|
||||
colorReset)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: %s: %s\n", msg.source.PrettyPath, kind, msg.text)
|
||||
}
|
||||
|
||||
d := detailStruct(msg, terminalInfo)
|
||||
|
||||
if terminalInfo.UseColor {
|
||||
return fmt.Sprintf("%s%s:%d:%d: %s%s: %s%s\n%s%s%s%s%s%s\n%s%s%s%s\n",
|
||||
colorBold, d.Path,
|
||||
d.Line,
|
||||
d.Column,
|
||||
kindColor, d.Kind,
|
||||
colorResetBold, d.Message,
|
||||
colorReset, d.SourceBefore, colorGreen, d.SourceMarked, colorReset, d.SourceAfter,
|
||||
colorGreen, d.Indent, d.Marker,
|
||||
colorReset)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d:%d: %s: %s\n%s\n%s%s\n",
|
||||
d.Path, d.Line, d.Column, d.Kind, d.Message, d.Source, d.Indent, d.Marker)
|
||||
}
|
||||
|
||||
type MsgDetail struct {
|
||||
Path string
|
||||
Line int
|
||||
Column int
|
||||
Kind string
|
||||
Message string
|
||||
|
||||
// Source == SourceBefore + SourceMarked + SourceAfter
|
||||
Source string
|
||||
SourceBefore string
|
||||
SourceMarked string
|
||||
SourceAfter string
|
||||
|
||||
Indent string
|
||||
Marker string
|
||||
}
|
||||
|
||||
func detailStruct(msg Msg, terminalInfo TerminalInfo) MsgDetail {
|
||||
var prevCodePoint rune
|
||||
contents := msg.source.Contents
|
||||
lineStart := 0
|
||||
lineCount := 0
|
||||
|
||||
for i, codePoint := range contents[0:msg.start] {
|
||||
switch codePoint {
|
||||
case '\n':
|
||||
lineStart = i + 1
|
||||
if prevCodePoint != '\r' {
|
||||
lineCount++
|
||||
}
|
||||
case '\r', '\u2028', '\u2029':
|
||||
lineStart = i + 1
|
||||
}
|
||||
prevCodePoint = codePoint
|
||||
}
|
||||
|
||||
lineEnd := len(contents)
|
||||
|
||||
loop:
|
||||
for i, codePoint := range contents[lineStart:] {
|
||||
switch codePoint {
|
||||
case '\r', '\n', '\u2028', '\u2029':
|
||||
lineEnd = lineStart + i
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
spacesPerTab := 2
|
||||
columnCount := int(msg.start) - lineStart
|
||||
lineText := renderTabStops(contents[lineStart:lineEnd], spacesPerTab)
|
||||
indent := strings.Repeat(" ", len(renderTabStops(contents[lineStart:msg.start], spacesPerTab)))
|
||||
marker := "^"
|
||||
markerStart := len(indent)
|
||||
markerEnd := len(indent)
|
||||
|
||||
// Extend markers to cover the full range of the error
|
||||
if msg.length > 0 {
|
||||
markerEnd = len(renderTabStops(contents[lineStart:msg.start+msg.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
|
||||
if terminalInfo.Width > 0 && len(lineText) > terminalInfo.Width {
|
||||
// Try to center the error
|
||||
sliceStart := (markerStart + markerEnd - terminalInfo.Width) / 2
|
||||
if sliceStart > markerStart-terminalInfo.Width/5 {
|
||||
sliceStart = markerStart - terminalInfo.Width/5
|
||||
}
|
||||
if sliceStart < 0 {
|
||||
sliceStart = 0
|
||||
}
|
||||
if sliceStart > len(lineText)-terminalInfo.Width {
|
||||
sliceStart = len(lineText) - terminalInfo.Width
|
||||
}
|
||||
sliceEnd := sliceStart + terminalInfo.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
|
||||
indent = strings.Repeat(" ", markerStart)
|
||||
lineText = slicedLine
|
||||
}
|
||||
|
||||
// If marker is still multi-character after clipping, make the marker wider
|
||||
if markerEnd-markerStart > 1 {
|
||||
marker = strings.Repeat("~", markerEnd-markerStart)
|
||||
}
|
||||
|
||||
kind := "error"
|
||||
if msg.kind == Warning {
|
||||
kind = "warning"
|
||||
}
|
||||
|
||||
return MsgDetail{
|
||||
Path: msg.source.PrettyPath,
|
||||
Line: lineCount + 1,
|
||||
Column: columnCount,
|
||||
Kind: kind,
|
||||
Message: msg.text,
|
||||
|
||||
Source: lineText,
|
||||
SourceBefore: lineText[:markerStart],
|
||||
SourceMarked: lineText[markerStart:markerEnd],
|
||||
SourceAfter: lineText[markerEnd:],
|
||||
|
||||
Indent: indent,
|
||||
Marker: marker,
|
||||
}
|
||||
}
|
||||
|
||||
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(source Source, loc ast.Loc, text string) {
|
||||
log.msgs <- Msg{source, loc.Start, 0, text, Error}
|
||||
}
|
||||
|
||||
func (log Log) AddWarning(source Source, loc ast.Loc, text string) {
|
||||
log.msgs <- Msg{source, loc.Start, 0, text, Warning}
|
||||
}
|
||||
|
||||
func (log Log) AddRangeError(source Source, r ast.Range, text string) {
|
||||
log.msgs <- Msg{source, r.Loc.Start, r.Len, text, Error}
|
||||
}
|
||||
|
||||
func (log Log) AddRangeWarning(source Source, r ast.Range, text string) {
|
||||
log.msgs <- Msg{source, r.Loc.Start, r.Len, text, Warning}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"esbuild/ast"
|
||||
"esbuild/bundler"
|
||||
"esbuild/lexer"
|
||||
"esbuild/logging"
|
||||
"esbuild/parser"
|
||||
"esbuild/resolver"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
traceFile string
|
||||
cpuprofileFile string
|
||||
parseOptions parser.ParseOptions
|
||||
bundleOptions bundler.BundleOptions
|
||||
logOptions logging.StderrOptions
|
||||
entryPaths []string
|
||||
}
|
||||
|
||||
func (args *args) exitWithError(text string) {
|
||||
colorRed := ""
|
||||
colorBold := ""
|
||||
colorReset := ""
|
||||
|
||||
if logging.StdinTerminalInfo().UseColor {
|
||||
colorRed = "\033[1;31m"
|
||||
colorBold = "\033[0;1m"
|
||||
colorReset = "\033[0m"
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%serror: %s%s%s\n", colorRed, colorBold, text, colorReset)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (args *args) parseDefine(key string, value string) bool {
|
||||
// The key must be a dot-separated identifier list
|
||||
for _, part := range strings.Split(key, ".") {
|
||||
if !lexer.IsIdentifier(part) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the value as JSON
|
||||
log, done := logging.NewDeferLog()
|
||||
source := logging.Source{Contents: value}
|
||||
expr, ok := parser.ParseJson(log, source)
|
||||
done()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only allow atoms for now
|
||||
switch expr.Data.(type) {
|
||||
case *ast.ENull, *ast.EBoolean, *ast.EString, *ast.ENumber:
|
||||
args.parseOptions.Defines[key] = expr.Data
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (args *args) parseMemberExpression(text string) ([]string, bool) {
|
||||
parts := strings.Split(text, ".")
|
||||
for _, part := range parts {
|
||||
if !lexer.IsIdentifier(part) {
|
||||
return parts, false
|
||||
}
|
||||
}
|
||||
return parts, true
|
||||
}
|
||||
|
||||
func parseArgs() args {
|
||||
args := args{
|
||||
parseOptions: parser.ParseOptions{
|
||||
Defines: make(map[string]ast.E),
|
||||
},
|
||||
bundleOptions: bundler.BundleOptions{},
|
||||
logOptions: logging.StderrOptions{
|
||||
IncludeSource: true,
|
||||
ErrorLimit: 10,
|
||||
ExitWhenLimitIsHit: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Show usage information if called with no arguments
|
||||
showHelp := len(os.Args) < 2
|
||||
|
||||
// Show help if a common help flag is provided
|
||||
for _, arg := range os.Args {
|
||||
if arg == "-h" || arg == "-help" || arg == "--help" || arg == "/?" {
|
||||
showHelp = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Show help and exit if requested
|
||||
if showHelp {
|
||||
fmt.Print(`
|
||||
Usage:
|
||||
esbuild [options] [entry points]
|
||||
|
||||
Options:
|
||||
--name=... The name of the module
|
||||
--bundle Bundle all dependencies into the output files
|
||||
--outfile=... The output file (for one entry point)
|
||||
--outdir=... The output directory (for multiple entry points)
|
||||
--sourcemap Emit a source map
|
||||
--error-limit=... Maximum error count or 0 to disable (default 10)
|
||||
|
||||
--minify Sets all --minify-* flags
|
||||
--minify-whitespace Remove whitespace
|
||||
--minify-identifiers Shorten identifiers
|
||||
--minify-syntax Use equivalent but shorter syntax
|
||||
|
||||
--define:K=V Substitute K with V while parsing
|
||||
--jsx-factory=... What to use instead of React.createElement
|
||||
--jsx-fragment=... What to use instead of React.Fragment
|
||||
|
||||
--trace=... Write a CPU trace to this file
|
||||
--cpuprofile=... Write a CPU profile to this file
|
||||
|
||||
Example:
|
||||
# Produces dist/entry_point.js and dist/entry_point.js.map
|
||||
esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap
|
||||
|
||||
`)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
for _, arg := range os.Args[1:] {
|
||||
switch {
|
||||
case arg == "--bundle":
|
||||
args.parseOptions.ScanForDependencies = true
|
||||
args.bundleOptions.Bundle = true
|
||||
|
||||
case arg == "--minify":
|
||||
args.parseOptions.MangleSyntax = true
|
||||
args.bundleOptions.MangleSyntax = true
|
||||
args.bundleOptions.RemoveWhitespace = true
|
||||
args.bundleOptions.MinifyIdentifiers = true
|
||||
|
||||
case arg == "--minify-syntax":
|
||||
args.parseOptions.MangleSyntax = true
|
||||
args.bundleOptions.MangleSyntax = true
|
||||
|
||||
case arg == "--minify-whitespace":
|
||||
args.bundleOptions.RemoveWhitespace = true
|
||||
|
||||
case arg == "--minify-identifiers":
|
||||
args.bundleOptions.MinifyIdentifiers = true
|
||||
|
||||
case arg == "--sourcemap":
|
||||
args.bundleOptions.SourceMap = true
|
||||
|
||||
case strings.HasPrefix(arg, "--error-limit="):
|
||||
value, err := strconv.Atoi(arg[len("--error-limit="):])
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Invalid error limit: %s", arg))
|
||||
}
|
||||
args.logOptions.ErrorLimit = value
|
||||
|
||||
case strings.HasPrefix(arg, "--name="):
|
||||
value := arg[len("--name="):]
|
||||
if !lexer.IsIdentifier(value) {
|
||||
args.exitWithError(fmt.Sprintf("Invalid name: %s", arg))
|
||||
}
|
||||
args.bundleOptions.ModuleName = value
|
||||
|
||||
case strings.HasPrefix(arg, "--outfile="):
|
||||
value := arg[len("--outfile="):]
|
||||
file, err := filepath.Abs(value)
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Invalid output file: %s", arg))
|
||||
}
|
||||
args.bundleOptions.AbsOutputFile = file
|
||||
|
||||
case strings.HasPrefix(arg, "--outdir="):
|
||||
value := arg[len("--outdir="):]
|
||||
dir, err := filepath.Abs(value)
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Invalid output directory: %s", arg))
|
||||
}
|
||||
args.bundleOptions.AbsOutputDir = dir
|
||||
|
||||
case strings.HasPrefix(arg, "--define:"):
|
||||
text := arg[len("--define:"):]
|
||||
equals := strings.IndexByte(text, '=')
|
||||
if equals == -1 {
|
||||
args.exitWithError(fmt.Sprintf("Missing '=': %s", arg))
|
||||
}
|
||||
if !args.parseDefine(text[:equals], text[equals+1:]) {
|
||||
args.exitWithError(fmt.Sprintf("Invalid define: %s", arg))
|
||||
}
|
||||
|
||||
case strings.HasPrefix(arg, "--jsx-factory="):
|
||||
if parts, ok := args.parseMemberExpression(arg[len("--jsx-factory="):]); ok {
|
||||
args.parseOptions.JSX.Factory = parts
|
||||
} else {
|
||||
args.exitWithError(fmt.Sprintf("Invalid JSX factory: %s", arg))
|
||||
}
|
||||
|
||||
case strings.HasPrefix(arg, "--jsx-fragment="):
|
||||
if parts, ok := args.parseMemberExpression(arg[len("--jsx-fragment="):]); ok {
|
||||
args.parseOptions.JSX.Fragment = parts
|
||||
} else {
|
||||
args.exitWithError(fmt.Sprintf("Invalid JSX fragment: %s", arg))
|
||||
}
|
||||
|
||||
case strings.HasPrefix(arg, "--trace="):
|
||||
args.traceFile = arg[len("--trace="):]
|
||||
|
||||
case strings.HasPrefix(arg, "--cpuprofile="):
|
||||
args.cpuprofileFile = arg[len("--cpuprofile="):]
|
||||
|
||||
case strings.HasPrefix(arg, "-"):
|
||||
args.exitWithError(fmt.Sprintf("Invalid flag: %s", arg))
|
||||
|
||||
default:
|
||||
arg, err := filepath.Abs(arg)
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Invalid path: %s", arg))
|
||||
}
|
||||
args.entryPaths = append(args.entryPaths, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if args.bundleOptions.AbsOutputFile != "" && len(args.entryPaths) > 1 {
|
||||
args.exitWithError("Use --outdir instead of --outfile when there are multiple entry points")
|
||||
}
|
||||
|
||||
if args.bundleOptions.AbsOutputFile != "" && args.bundleOptions.AbsOutputDir != "" {
|
||||
args.exitWithError("Cannot use both --outfile and --outdir")
|
||||
}
|
||||
|
||||
if args.bundleOptions.AbsOutputFile != "" {
|
||||
// If the output file is specified, use it to derive the output directory
|
||||
args.bundleOptions.AbsOutputDir = filepath.Dir(args.bundleOptions.AbsOutputFile)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func main() {
|
||||
start := time.Now()
|
||||
args := parseArgs()
|
||||
|
||||
// Show usage information if called with no files
|
||||
if len(args.entryPaths) == 0 {
|
||||
args.exitWithError("No files specified")
|
||||
}
|
||||
|
||||
// Capture the defer statements below so the "done" message comes last
|
||||
func() {
|
||||
// To view a CPU trace, use "go tool trace [file]". Note that the trace
|
||||
// viewer doesn't work under Windows Subsystem for Linux for some reason.
|
||||
if args.traceFile != "" {
|
||||
f, err := os.Create(args.traceFile)
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Failed to create a file called '%s': %s", args.traceFile, err.Error()))
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
fmt.Fprintf(os.Stderr, "Wrote to %s\n", args.traceFile)
|
||||
}()
|
||||
trace.Start(f)
|
||||
defer trace.Stop()
|
||||
}
|
||||
|
||||
// To view a CPU profile, drop the file into https://speedscope.app.
|
||||
// Note: Running the CPU profiler doesn't work under Windows subsystem for
|
||||
// Linux. The profiler has to be built for native Windows and run using the
|
||||
// command prompt instead.
|
||||
if args.cpuprofileFile != "" {
|
||||
f, err := os.Create(args.cpuprofileFile)
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Failed to create a file called '%s': %s", args.cpuprofileFile, err.Error()))
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
fmt.Fprintf(os.Stderr, "Wrote to %s\n", args.cpuprofileFile)
|
||||
}()
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Parse all files in the bundle
|
||||
resolver := resolver.CreateFileSystemResolver()
|
||||
log, join := logging.NewStderrLog(args.logOptions)
|
||||
bundle := bundler.ScanBundle(log, resolver, args.entryPaths, args.parseOptions)
|
||||
|
||||
// Stop now if there were errors
|
||||
if join().Errors != 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Generate the results
|
||||
log2, join2 := logging.NewStderrLog(args.logOptions)
|
||||
result := bundle.Compile(log2, args.bundleOptions)
|
||||
|
||||
// Stop now if there were errors
|
||||
if join2().Errors != 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create the output directory
|
||||
if args.bundleOptions.AbsOutputDir != "" {
|
||||
if err := os.MkdirAll(args.bundleOptions.AbsOutputDir, 0755); err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Cannot create output directory: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the results
|
||||
for _, item := range result {
|
||||
// Write out the JavaScript file
|
||||
err := ioutil.WriteFile(item.JsAbsPath, []byte(item.JsContents), 0644)
|
||||
path := resolver.PrettyPath(item.JsAbsPath)
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Failed to write to %s (%s)", path, err.Error()))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Wrote to %s (%s)\n", path, toSize(len(item.JsContents)))
|
||||
|
||||
// Also write the source map
|
||||
if args.bundleOptions.SourceMap {
|
||||
err := ioutil.WriteFile(item.SourceMapAbsPath, item.SourceMapContents, 0644)
|
||||
path := resolver.PrettyPath(item.SourceMapAbsPath)
|
||||
if err != nil {
|
||||
args.exitWithError(fmt.Sprintf("Failed to write to %s: (%s)", path, err.Error()))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Wrote to %s (%s)\n", path, toSize(len(item.SourceMapContents)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Done in %dms\n", time.Since(start).Nanoseconds()/1000000)
|
||||
}
|
||||
|
||||
func toSize(bytes int) string {
|
||||
if bytes < 1024 {
|
||||
return fmt.Sprintf("%d bytes", bytes)
|
||||
}
|
||||
|
||||
if bytes < 1024*1024 {
|
||||
return fmt.Sprintf("%.1fkb", float32(bytes)/float32(1024))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.1fmb", float32(bytes)/float32(1024*1024))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,129 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"esbuild/ast"
|
||||
"esbuild/lexer"
|
||||
"esbuild/logging"
|
||||
)
|
||||
|
||||
type jsonParser struct {
|
||||
log logging.Log
|
||||
source logging.Source
|
||||
lexer lexer.Lexer
|
||||
}
|
||||
|
||||
func (p *jsonParser) parseExpr() ast.Expr {
|
||||
loc := p.lexer.Loc()
|
||||
|
||||
switch p.lexer.Token {
|
||||
case lexer.TFalse:
|
||||
p.lexer.Next()
|
||||
return ast.Expr{loc, &ast.EBoolean{false}}
|
||||
|
||||
case lexer.TTrue:
|
||||
p.lexer.Next()
|
||||
return ast.Expr{loc, &ast.EBoolean{true}}
|
||||
|
||||
case lexer.TNull:
|
||||
p.lexer.Next()
|
||||
return ast.Expr{loc, &ast.ENull{}}
|
||||
|
||||
case lexer.TStringLiteral:
|
||||
value := p.lexer.StringLiteral
|
||||
p.lexer.Next()
|
||||
return ast.Expr{loc, &ast.EString{value}}
|
||||
|
||||
case lexer.TNumericLiteral:
|
||||
value := p.lexer.Number
|
||||
p.lexer.Next()
|
||||
return ast.Expr{loc, &ast.ENumber{value}}
|
||||
|
||||
case lexer.TOpenBracket:
|
||||
p.lexer.Next()
|
||||
items := []ast.Expr{}
|
||||
|
||||
for p.lexer.Token != lexer.TCloseBracket {
|
||||
item := p.parseExpr()
|
||||
items = append(items, item)
|
||||
|
||||
if p.lexer.Token != lexer.TComma {
|
||||
break
|
||||
}
|
||||
p.lexer.Next()
|
||||
}
|
||||
|
||||
p.lexer.Expect(lexer.TCloseBracket)
|
||||
return ast.Expr{loc, &ast.EArray{items}}
|
||||
|
||||
case lexer.TOpenBrace:
|
||||
p.lexer.Next()
|
||||
properties := []ast.Property{}
|
||||
|
||||
for p.lexer.Token != lexer.TCloseBrace {
|
||||
key := ast.Expr{p.lexer.Loc(), &ast.EString{p.lexer.StringLiteral}}
|
||||
p.lexer.Expect(lexer.TStringLiteral)
|
||||
p.lexer.Expect(lexer.TColon)
|
||||
value := p.parseExpr()
|
||||
|
||||
property := ast.Property{
|
||||
Kind: ast.PropertyNormal,
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
properties = append(properties, property)
|
||||
|
||||
if p.lexer.Token != lexer.TComma {
|
||||
break
|
||||
}
|
||||
p.lexer.Next()
|
||||
}
|
||||
|
||||
p.lexer.Expect(lexer.TCloseBrace)
|
||||
return ast.Expr{loc, &ast.EObject{properties}}
|
||||
|
||||
default:
|
||||
p.lexer.Unexpected()
|
||||
return ast.Expr{}
|
||||
}
|
||||
}
|
||||
|
||||
func ParseJson(log logging.Log, source logging.Source) (result ast.Expr, ok bool) {
|
||||
ok = true
|
||||
defer func() {
|
||||
r := recover()
|
||||
if _, isLexerPanic := r.(lexer.LexerPanic); isLexerPanic {
|
||||
ok = false
|
||||
} else if r != nil {
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
p := &jsonParser{
|
||||
log: log,
|
||||
source: source,
|
||||
lexer: lexer.NewLexer(log, source),
|
||||
}
|
||||
|
||||
result = p.parseExpr()
|
||||
return
|
||||
}
|
||||
|
||||
func ModuleExportsAST(source logging.Source, expr ast.Expr) ast.AST {
|
||||
b := newBinder(source)
|
||||
|
||||
// Make a symbol map that contains our file's symbols
|
||||
symbols := ast.SymbolMap{make([][]ast.Symbol, source.Index+1)}
|
||||
symbols.Outer[source.Index] = b.symbols
|
||||
|
||||
// "module.exports = [expr]"
|
||||
stmt := ast.Stmt{expr.Loc, &ast.SExpr{ast.Expr{expr.Loc, &ast.EBinary{
|
||||
ast.BinOpAssign,
|
||||
ast.Expr{expr.Loc, &ast.EDot{ast.Expr{expr.Loc, &ast.EIdentifier{b.moduleRef}}, "exports", expr.Loc, false}},
|
||||
expr,
|
||||
}}}}
|
||||
|
||||
// Mark that we used the "module" variable
|
||||
b.symbols[b.moduleRef.InnerIndex].UseCountEstimate++
|
||||
|
||||
return b.toAST(source, []ast.Stmt{stmt}, []ast.ImportPath{})
|
||||
}
|
||||
@@ -0,0 +1,944 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"esbuild/ast"
|
||||
"esbuild/logging"
|
||||
"esbuild/printer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertEqual(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Fatalf("%s != %s", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func expectParseError(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
Parse(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
}, ParseOptions{})
|
||||
msgs := join()
|
||||
text := ""
|
||||
for _, msg := range msgs {
|
||||
text += msg.String(logging.StderrOptions{}, logging.TerminalInfo{})
|
||||
}
|
||||
assertEqual(t, text, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func expectPrinted(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
ast, ok := Parse(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
}, ParseOptions{
|
||||
OmitWarnings: true,
|
||||
})
|
||||
msgs := join()
|
||||
text := ""
|
||||
for _, msg := range msgs {
|
||||
text += msg.String(logging.StderrOptions{}, logging.TerminalInfo{})
|
||||
}
|
||||
assertEqual(t, text, "")
|
||||
if !ok {
|
||||
t.Fatal("Parse error")
|
||||
}
|
||||
js, _ := printer.Print(ast, printer.Options{})
|
||||
assertEqual(t, string(js), expected)
|
||||
})
|
||||
}
|
||||
|
||||
func expectPrintedMangle(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
ast, ok := Parse(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
}, ParseOptions{
|
||||
MangleSyntax: true,
|
||||
})
|
||||
msgs := join()
|
||||
text := ""
|
||||
for _, msg := range msgs {
|
||||
text += msg.String(logging.StderrOptions{}, logging.TerminalInfo{})
|
||||
}
|
||||
assertEqual(t, text, "")
|
||||
if !ok {
|
||||
t.Fatal("Parse error")
|
||||
}
|
||||
js, _ := printer.Print(ast, printer.Options{})
|
||||
assertEqual(t, string(js), expected)
|
||||
})
|
||||
}
|
||||
|
||||
func expectParseErrorJSX(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
Parse(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
}, ParseOptions{
|
||||
JSX: JSXOptions{
|
||||
Parse: true,
|
||||
},
|
||||
})
|
||||
msgs := join()
|
||||
text := ""
|
||||
for _, msg := range msgs {
|
||||
text += msg.String(logging.StderrOptions{}, logging.TerminalInfo{})
|
||||
}
|
||||
assertEqual(t, text, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func expectPrintedJSX(t *testing.T, contents string, expected string) {
|
||||
t.Run(contents, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
ast, ok := Parse(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
}, ParseOptions{
|
||||
JSX: JSXOptions{
|
||||
Parse: true,
|
||||
},
|
||||
})
|
||||
msgs := join()
|
||||
text := ""
|
||||
for _, msg := range msgs {
|
||||
text += msg.String(logging.StderrOptions{}, logging.TerminalInfo{})
|
||||
}
|
||||
assertEqual(t, text, "")
|
||||
if !ok {
|
||||
t.Fatal("Parse error")
|
||||
}
|
||||
js, _ := printer.Print(ast, printer.Options{})
|
||||
assertEqual(t, string(js), expected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBinOp(t *testing.T) {
|
||||
for code, entry := range ast.OpTable {
|
||||
if ast.OpCode(code).IsLeftAssociative() {
|
||||
op := entry.Text
|
||||
expectPrinted(t, "a "+op+" b "+op+" c", "a "+op+" b "+op+" c;\n")
|
||||
expectPrinted(t, "(a "+op+" b) "+op+" c", "a "+op+" b "+op+" c;\n")
|
||||
expectPrinted(t, "a "+op+" (b "+op+" c)", "a "+op+" (b "+op+" c);\n")
|
||||
}
|
||||
|
||||
if ast.OpCode(code).IsRightAssociative() {
|
||||
op := entry.Text
|
||||
expectPrinted(t, "a "+op+" b "+op+" c", "a "+op+" b "+op+" c;\n")
|
||||
expectPrinted(t, "(a "+op+" b) "+op+" c", "(a "+op+" b) "+op+" c;\n")
|
||||
expectPrinted(t, "a "+op+" (b "+op+" c)", "a "+op+" b "+op+" c;\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegExp(t *testing.T) {
|
||||
expectPrinted(t, "/x/g", "/x/g;\n")
|
||||
expectPrinted(t, "/x/i", "/x/i;\n")
|
||||
expectPrinted(t, "/x/m", "/x/m;\n")
|
||||
expectPrinted(t, "/x/s", "/x/s;\n")
|
||||
expectPrinted(t, "/x/u", "/x/u;\n")
|
||||
expectPrinted(t, "/x/y", "/x/y;\n")
|
||||
}
|
||||
|
||||
func TestIdentifierEscapes(t *testing.T) {
|
||||
expectPrinted(t, "var _\\u0076\\u0061\\u0072", "var _var;\n")
|
||||
expectParseError(t, "var \\u0076\\u0061\\u0072", "<stdin>: error: Expected identifier but found \"\\\\u0076\\\\u0061\\\\u0072\"\n")
|
||||
expectParseError(t, "\\u0076\\u0061\\u0072 foo", "<stdin>: error: Unexpected \"\\\\u0076\\\\u0061\\\\u0072\"\n")
|
||||
|
||||
expectPrinted(t, "foo._\\u0076\\u0061\\u0072", "foo._var;\n")
|
||||
expectPrinted(t, "foo.\\u0076\\u0061\\u0072", "foo.var;\n")
|
||||
}
|
||||
|
||||
func TestDecls(t *testing.T) {
|
||||
expectParseError(t, "var x = 0", "")
|
||||
expectParseError(t, "let x = 0", "")
|
||||
expectParseError(t, "const x = 0", "")
|
||||
expectParseError(t, "for (var x = 0;;) ;", "")
|
||||
expectParseError(t, "for (let x = 0;;) ;", "")
|
||||
expectParseError(t, "for (const x = 0;;) ;", "")
|
||||
|
||||
expectParseError(t, "for (var x in y) ;", "")
|
||||
expectParseError(t, "for (let x in y) ;", "")
|
||||
expectParseError(t, "for (const x in y) ;", "")
|
||||
expectParseError(t, "for (var x of y) ;", "")
|
||||
expectParseError(t, "for (let x of y) ;", "")
|
||||
expectParseError(t, "for (const x of y) ;", "")
|
||||
|
||||
expectParseError(t, "var x", "")
|
||||
expectParseError(t, "let x", "")
|
||||
expectParseError(t, "const x", "<stdin>: error: This constant must be initialized\n")
|
||||
expectParseError(t, "for (var x;;) ;", "")
|
||||
expectParseError(t, "for (let x;;) ;", "")
|
||||
expectParseError(t, "for (const x;;) ;", "<stdin>: error: This constant must be initialized\n")
|
||||
|
||||
// Make sure bindings are visited during parsing
|
||||
expectPrinted(t, "var {[x]: y} = {}", "var {[x]: y} = {};\n")
|
||||
expectPrinted(t, "var {...x} = {}", "var {...x} = {};\n")
|
||||
|
||||
// Test destructuring patterns
|
||||
expectPrinted(t, "var [...x] = []", "var [...x] = [];\n")
|
||||
expectPrinted(t, "var {...x} = {}", "var {...x} = {};\n")
|
||||
expectPrinted(t, "([...x] = []) => {}", "([...x] = []) => {\n};\n")
|
||||
expectPrinted(t, "({...x} = {}) => {}", "({...x} = {}) => {\n};\n")
|
||||
|
||||
expectParseError(t, "var [...x,] = []", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "var {...x,} = {}", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "([...x,] = []) => {}", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "({...x,} = {}) => {}", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
|
||||
expectPrinted(t, "[b, ...c] = d", "[b, ...c] = d;\n")
|
||||
expectPrinted(t, "([b, ...c] = d)", "[b, ...c] = d;\n")
|
||||
expectPrinted(t, "({b, ...c} = d)", "({\n b,\n ...c\n} = d);\n")
|
||||
expectPrinted(t, "({a = b} = c)", "({\n a = b\n} = c);\n")
|
||||
expectPrinted(t, "({a: b = c} = d)", "({\n a: b = c\n} = d);\n")
|
||||
expectPrinted(t, "({a: b.c} = d)", "({\n a: b.c\n} = d);\n")
|
||||
expectPrinted(t, "[a = {}] = b", "[a = {}] = b;\n")
|
||||
|
||||
expectParseError(t, "[b, ...c,] = d", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "([b, ...c,] = d)", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "({b, ...c,} = d)", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "({a = b})", "<stdin>: error: Unexpected \"=\"\n")
|
||||
expectParseError(t, "({a = b}) = c", "<stdin>: error: Unexpected \"=\"\n")
|
||||
expectParseError(t, "[a = {b = c}] = d", "<stdin>: error: Unexpected \"=\"\n")
|
||||
|
||||
expectPrinted(t, "for ([{a = {}}] in b) {}", "for ([{\n a = {}\n}] in b) {\n}\n")
|
||||
expectPrinted(t, "for ([{a = {}}] of b) {}", "for ([{\n a = {}\n}] of b) {\n}\n")
|
||||
expectPrinted(t, "for ({a = {}} in b) {}", "for ({\n a = {}\n} in b) {\n}\n")
|
||||
expectPrinted(t, "for ({a = {}} of b) {}", "for ({\n a = {}\n} of b) {\n}\n")
|
||||
|
||||
expectParseError(t, "({a = {}} in b)", "<stdin>: error: Unexpected \"=\"\n")
|
||||
expectParseError(t, "[{a = {}}]\nof()", "<stdin>: error: Unexpected \"=\"\n")
|
||||
expectParseError(t, "for ([...a, b] in c) {}", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "for ([...a, b] of c) {}", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
}
|
||||
|
||||
func TestFor(t *testing.T) {
|
||||
expectParseError(t, "for (; in x) ;", "<stdin>: error: Unexpected \"in\"\n")
|
||||
expectParseError(t, "for (; of x) ;", "<stdin>: error: Expected \";\" but found \"x\"\n")
|
||||
expectParseError(t, "for (; in; ) ;", "<stdin>: error: Unexpected \"in\"\n")
|
||||
expectPrinted(t, "for (; of; ) ;", "for (; of; )\n ;\n")
|
||||
|
||||
expectPrinted(t, "for (a in b) ;", "for (a in b)\n ;\n")
|
||||
expectPrinted(t, "for (var a in b) ;", "for (var a in b)\n ;\n")
|
||||
expectPrinted(t, "for (let a in b) ;", "for (let a in b)\n ;\n")
|
||||
expectPrinted(t, "for (const a in b) ;", "for (const a in b)\n ;\n")
|
||||
expectParseError(t, "for (var a, b in b) ;", "<stdin>: error: for-in loops must have a single declaration\n")
|
||||
expectParseError(t, "for (let a, b in b) ;", "<stdin>: error: for-in loops must have a single declaration\n")
|
||||
expectParseError(t, "for (const a, b in b) ;", "<stdin>: error: for-in loops must have a single declaration\n")
|
||||
|
||||
expectPrinted(t, "for (a of b) ;", "for (a of b)\n ;\n")
|
||||
expectPrinted(t, "for (var a of b) ;", "for (var a of b)\n ;\n")
|
||||
expectPrinted(t, "for (let a of b) ;", "for (let a of b)\n ;\n")
|
||||
expectPrinted(t, "for (const a of b) ;", "for (const a of b)\n ;\n")
|
||||
expectParseError(t, "for (var a, b of b) ;", "<stdin>: error: for-of loops must have a single declaration\n")
|
||||
expectParseError(t, "for (let a, b of b) ;", "<stdin>: error: for-of loops must have a single declaration\n")
|
||||
expectParseError(t, "for (const a, b of b) ;", "<stdin>: error: for-of loops must have a single declaration\n")
|
||||
|
||||
expectPrinted(t, "for (var x = 0 in y) ;", "for (var x = 0 in y)\n ;\n") // This is a weird special-case
|
||||
expectParseError(t, "for (let x = 0 in y) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (const x = 0 in y) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (var x = 0 of y) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (let x = 0 of y) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (const x = 0 of y) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
|
||||
expectParseError(t, "for (var [x] = y in z) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (let [x] = y in z) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (const [x] = y in z) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (var [x] = y of z) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (let [x] = y of z) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (const [x] = y of z) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
|
||||
expectParseError(t, "for (var {x} = y in z) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (let {x} = y in z) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (const {x} = y in z) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (var {x} = y of z) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (let {x} = y of z) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
expectParseError(t, "for (const {x} = y of z) ;", "<stdin>: error: for-of loop variables cannot have an initializer\n")
|
||||
|
||||
// Make sure "in" rules are disabled
|
||||
expectPrinted(t, "for (var {[x in y]: z} = {};;);", "for (var {[x in y]: z} = {}; ; )\n ;\n")
|
||||
expectPrinted(t, "for (var {x = y in z} = {};;);", "for (var {x = y in z} = {}; ; )\n ;\n")
|
||||
expectPrinted(t, "for (var {x: y = z in w} = {};;);", "for (var {x: y = z in w} = {}; ; )\n ;\n")
|
||||
expectPrinted(t, "for (var x = (a in b);;);", "for (var x = a in b; ; )\n ;\n")
|
||||
expectPrinted(t, "for (var x = [a in b];;);", "for (var x = [a in b]; ; )\n ;\n")
|
||||
expectPrinted(t, "for (var x = y(a in b);;);", "for (var x = y(a in b); ; )\n ;\n")
|
||||
expectPrinted(t, "for (var x = {y: a in b};;);", "for (var x = {\n y: a in b\n}; ; )\n ;\n")
|
||||
expectPrinted(t, "for (a ? b in c : d;;);", "for (a ? b in c : d; ; )\n ;\n")
|
||||
expectPrinted(t, "for (var x = class { [a in b]() {} };;);", "for (var x = class {\n [a in b]() {\n }\n}; ; )\n ;\n")
|
||||
expectParseError(t, "for (var x = class extends a in b {};;);", "<stdin>: error: Expected \"{\" but found \"in\"\n")
|
||||
}
|
||||
|
||||
func TestScope(t *testing.T) {
|
||||
expectParseError(t, "var x; var y", "")
|
||||
expectParseError(t, "var x; let y", "")
|
||||
expectParseError(t, "let x; var y", "")
|
||||
expectParseError(t, "let x; let y", "")
|
||||
|
||||
expectParseError(t, "var x; var x", "")
|
||||
expectParseError(t, "var x; let x", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "let x; var x", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "let x; let x", "<stdin>: error: \"x\" has already been declared\n")
|
||||
|
||||
expectParseError(t, "var x; {var x}", "")
|
||||
expectParseError(t, "var x; {let x}", "")
|
||||
expectParseError(t, "let x; {var x}", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "let x; {let x}", "")
|
||||
|
||||
expectParseError(t, "{var x} var x", "")
|
||||
expectParseError(t, "{var x} let x", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "{let x} var x", "")
|
||||
expectParseError(t, "{let x} let x", "")
|
||||
|
||||
expectParseError(t, "{var x; {var x}}", "")
|
||||
expectParseError(t, "{var x; {let x}}", "")
|
||||
expectParseError(t, "{let x; {var x}}", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "{let x; {let x}}", "")
|
||||
|
||||
expectParseError(t, "{{var x} var x}", "")
|
||||
expectParseError(t, "{{var x} let x}", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "{{let x} var x}", "")
|
||||
expectParseError(t, "{{let x} let x}", "")
|
||||
|
||||
expectParseError(t, "{var x} {var x}", "")
|
||||
expectParseError(t, "{var x} {let x}", "")
|
||||
expectParseError(t, "{let x} {var x}", "")
|
||||
expectParseError(t, "{let x} {let x}", "")
|
||||
|
||||
expectParseError(t, "var x=1, x=2", "")
|
||||
expectParseError(t, "let x=1, x=2", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "const x=1, x=2", "<stdin>: error: \"x\" has already been declared\n")
|
||||
|
||||
expectParseError(t, "function foo(x) { var x }", "")
|
||||
expectParseError(t, "function foo(x) { let x }", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "function foo(x) { const x = 0 }", "<stdin>: error: \"x\" has already been declared\n")
|
||||
|
||||
expectParseError(t, "var x; function x() {}", "")
|
||||
expectParseError(t, "let x; function x() {}", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "function x() {} var x", "")
|
||||
expectParseError(t, "function x() {} let x", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "function x() {} function x() {}", "")
|
||||
|
||||
expectParseError(t, "var x; class x {}", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "let x; class x {}", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "class x {} var x", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "class x {} let x", "<stdin>: error: \"x\" has already been declared\n")
|
||||
expectParseError(t, "class x {} class x {}", "<stdin>: error: \"x\" has already been declared\n")
|
||||
}
|
||||
|
||||
func TestASI(t *testing.T) {
|
||||
expectParseError(t, "throw\n0", "<stdin>: error: Unexpected newline after \"throw\"\n")
|
||||
expectParseError(t, "return\n0", "<stdin>: warning: The following expression is not returned because of an automatically-inserted semicolon\n")
|
||||
expectPrinted(t, "return\n0", "return;\n0;\n")
|
||||
expectPrinted(t, "0\n[1]", "0[1];\n")
|
||||
expectPrinted(t, "0\n(1)", "0(1);\n")
|
||||
expectPrinted(t, "new x\n(1)", "new x(1);\n")
|
||||
expectPrinted(t, "while (true) break\nx", "while (true)\n break;\nx;\n")
|
||||
|
||||
expectPrinted(t, "function* foo(){yield\na}", "function* foo() {\n yield;\n a;\n}\n")
|
||||
expectParseError(t, "function* foo(){yield\n*a}", "<stdin>: error: Unexpected \"*\"\n")
|
||||
expectPrinted(t, "function* foo(){yield*\na}", "function* foo() {\n yield* a;\n}\n")
|
||||
|
||||
// This is a weird corner case where ASI applies without a newline
|
||||
expectPrinted(t, "do x;while(y)z", "do\n x;\nwhile (y);\nz;\n")
|
||||
expectPrinted(t, "{do x;while(y)}", "{\n do\n x;\n while (y);\n}\n")
|
||||
}
|
||||
|
||||
func TestArrays(t *testing.T) {
|
||||
expectPrinted(t, "[]", "[];\n")
|
||||
expectPrinted(t, "[,]", "[,];\n")
|
||||
expectPrinted(t, "[1]", "[1];\n")
|
||||
expectPrinted(t, "[1,]", "[1];\n")
|
||||
expectPrinted(t, "[,1]", "[, 1];\n")
|
||||
expectPrinted(t, "[1,2]", "[1, 2];\n")
|
||||
expectPrinted(t, "[,1,2]", "[, 1, 2];\n")
|
||||
expectPrinted(t, "[1,,2]", "[1, , 2];\n")
|
||||
expectPrinted(t, "[1,2,]", "[1, 2];\n")
|
||||
expectPrinted(t, "[1,2,,]", "[1, 2, ,];\n")
|
||||
}
|
||||
|
||||
func TestPattern(t *testing.T) {
|
||||
expectPrinted(t, "let {if: x} = y", "let {if: x} = y;\n")
|
||||
expectParseError(t, "let {x: if} = y", "<stdin>: error: Expected identifier but found \"if\"\n")
|
||||
}
|
||||
|
||||
func TestObject(t *testing.T) {
|
||||
expectPrinted(t, "({foo:0})", "({\n foo: 0\n});\n")
|
||||
expectPrinted(t, "({foo() {}})", "({\n foo() {\n }\n});\n")
|
||||
expectPrinted(t, "({*foo() {}})", "({\n *foo() {\n }\n});\n")
|
||||
expectPrinted(t, "({get foo() {}})", "({\n get foo() {\n }\n});\n")
|
||||
expectPrinted(t, "({set foo() {}})", "({\n set foo() {\n }\n});\n")
|
||||
|
||||
expectPrinted(t, "({if:0})", "({\n if: 0\n});\n")
|
||||
expectPrinted(t, "({if() {}})", "({\n if() {\n }\n});\n")
|
||||
expectPrinted(t, "({*if() {}})", "({\n *if() {\n }\n});\n")
|
||||
expectPrinted(t, "({get if() {}})", "({\n get if() {\n }\n});\n")
|
||||
expectPrinted(t, "({set if() {}})", "({\n set if() {\n }\n});\n")
|
||||
}
|
||||
|
||||
func TestClass(t *testing.T) {
|
||||
expectPrinted(t, "class Foo { foo() {} }", "class Foo {\n foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { *foo() {} }", "class Foo {\n *foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { get foo() {} }", "class Foo {\n get foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { set foo() {} }", "class Foo {\n set foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static foo() {} }", "class Foo {\n static foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static *foo() {} }", "class Foo {\n static *foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static get foo() {} }", "class Foo {\n static get foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static set foo() {} }", "class Foo {\n static set foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { async foo() {} }", "class Foo {\n async foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static async foo() {} }", "class Foo {\n static async foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static async *foo() {} }", "class Foo {\n static async *foo() {\n }\n}\n")
|
||||
expectParseError(t, "class Foo { async static foo() {} }", "<stdin>: error: Expected \"(\" but found \"foo\"\n")
|
||||
|
||||
expectPrinted(t, "class Foo { if() {} }", "class Foo {\n if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { *if() {} }", "class Foo {\n *if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { get if() {} }", "class Foo {\n get if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { set if() {} }", "class Foo {\n set if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static if() {} }", "class Foo {\n static if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static *if() {} }", "class Foo {\n static *if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static get if() {} }", "class Foo {\n static get if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static set if() {} }", "class Foo {\n static set if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { async if() {} }", "class Foo {\n async if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static async if() {} }", "class Foo {\n static async if() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static async *if() {} }", "class Foo {\n static async *if() {\n }\n}\n")
|
||||
expectParseError(t, "class Foo { async static if() {} }", "<stdin>: error: Expected \"(\" but found \"if\"\n")
|
||||
|
||||
expectPrinted(t, "class Foo { a() {} b() {} }", "class Foo {\n a() {\n }\n b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} get b() {} }", "class Foo {\n a() {\n }\n get b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} set b() {} }", "class Foo {\n a() {\n }\n set b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} static b() {} }", "class Foo {\n a() {\n }\n static b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} static *b() {} }", "class Foo {\n a() {\n }\n static *b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} static get b() {} }", "class Foo {\n a() {\n }\n static get b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} static set b() {} }", "class Foo {\n a() {\n }\n static set b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} async b() {} }", "class Foo {\n a() {\n }\n async b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} static async b() {} }", "class Foo {\n a() {\n }\n static async b() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { a() {} static async *b() {} }", "class Foo {\n a() {\n }\n static async *b() {\n }\n}\n")
|
||||
expectParseError(t, "class Foo { a() {} async static b() {} }", "<stdin>: error: Expected \"(\" but found \"b\"\n")
|
||||
}
|
||||
|
||||
func TestGenerator(t *testing.T) {
|
||||
expectParseError(t, "(class { * foo })", "<stdin>: error: Expected \"(\" but found \"}\"\n")
|
||||
expectParseError(t, "(class { * *foo() {} })", "<stdin>: error: Unexpected \"*\"\n")
|
||||
expectParseError(t, "(class { get*foo() {} })", "<stdin>: error: Unexpected \"*\"\n")
|
||||
expectParseError(t, "(class { set*foo() {} })", "<stdin>: error: Unexpected \"*\"\n")
|
||||
expectParseError(t, "(class { *get foo() {} })", "<stdin>: error: Expected \"(\" but found \"foo\"\n")
|
||||
expectParseError(t, "(class { *set foo() {} })", "<stdin>: error: Expected \"(\" but found \"foo\"\n")
|
||||
expectParseError(t, "(class { *static foo() {} })", "<stdin>: error: Expected \"(\" but found \"foo\"\n")
|
||||
|
||||
expectParseError(t, "function* foo() { -yield 100 }", "<stdin>: error: Cannot use a \"yield\" expression here without parentheses\n")
|
||||
expectPrinted(t, "function* foo() { -(yield 100) }", "function* foo() {\n -(yield 100);\n}\n")
|
||||
}
|
||||
|
||||
func TestAsync(t *testing.T) {
|
||||
expectPrinted(t, "function foo() { await }", "function foo() {\n await;\n}\n")
|
||||
expectPrinted(t, "async function foo() { await 0 }", "async function foo() {\n await 0;\n}\n")
|
||||
expectParseError(t, "async function() {}", "<stdin>: error: Expected identifier but found \"(\"\n")
|
||||
|
||||
expectPrinted(t, "-async function foo() { await 0 }", "-async function foo() {\n await 0;\n};\n")
|
||||
expectPrinted(t, "-async function() { await 0 }", "-async function() {\n await 0;\n};\n")
|
||||
expectPrinted(t, "1 - async function foo() { await 0 }", "1 - async function foo() {\n await 0;\n};\n")
|
||||
expectPrinted(t, "1 - async function() { await 0 }", "1 - async function() {\n await 0;\n};\n")
|
||||
expectPrinted(t, "(async function foo() { await 0 })", "(async function foo() {\n await 0;\n});\n")
|
||||
expectPrinted(t, "(async function() { await 0 })", "(async function() {\n await 0;\n});\n")
|
||||
expectPrinted(t, "(x, async function foo() { await 0 })", "x, async function foo() {\n await 0;\n};\n")
|
||||
expectPrinted(t, "(x, async function() { await 0 })", "x, async function() {\n await 0;\n};\n")
|
||||
|
||||
expectPrinted(t, "async", "async;\n")
|
||||
expectPrinted(t, "async + 1", "async + 1;\n")
|
||||
expectPrinted(t, "async => {}", "(async) => {\n};\n")
|
||||
expectPrinted(t, "(async, 1)", "async, 1;\n")
|
||||
expectPrinted(t, "(async, x) => {}", "(async, x) => {\n};\n")
|
||||
expectPrinted(t, "async ()", "async();\n")
|
||||
expectPrinted(t, "async (x)", "async(x);\n")
|
||||
expectPrinted(t, "async (...x)", "async(...x);\n")
|
||||
expectPrinted(t, "async (...x, ...y)", "async(...x, ...y);\n")
|
||||
expectPrinted(t, "async () => {}", "async () => {\n};\n")
|
||||
expectPrinted(t, "async x => {}", "async (x) => {\n};\n")
|
||||
expectPrinted(t, "async (x) => {}", "async (x) => {\n};\n")
|
||||
expectPrinted(t, "async (...x) => {}", "async (...x) => {\n};\n")
|
||||
expectPrinted(t, "async x => await 0", "async (x) => await 0;\n")
|
||||
expectPrinted(t, "async () => await 0", "async () => await 0;\n")
|
||||
expectParseError(t, "async x;", "<stdin>: error: Expected \"=>\" but found \";\"\n")
|
||||
expectParseError(t, "async (...x,) => {}", "<stdin>: error: Unexpected \",\" after rest pattern\n")
|
||||
expectParseError(t, "async => await 0", "<stdin>: error: Expected \";\" but found \"0\"\n")
|
||||
|
||||
expectParseError(t, "for await(;;);", "<stdin>: error: Cannot use \"await\" outside an async function\n")
|
||||
expectParseError(t, "async function foo(){for await(;;);}", "<stdin>: error: Unexpected \";\"\n")
|
||||
expectParseError(t, "async function foo(){for await(let x;;);}", "<stdin>: error: Expected \"of\" but found \";\"\n")
|
||||
expectPrinted(t, "async function foo(){for await(x of y);}", "async function foo() {\n for await (x of y)\n ;\n}\n")
|
||||
expectPrinted(t, "async function foo(){for await(let x of y);}", "async function foo() {\n for await (let x of y)\n ;\n}\n")
|
||||
}
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
expectPrinted(t, "{a:b}", "{\n a:\n b;\n}\n")
|
||||
expectPrinted(t, "({a:b})", "({\n a: b\n});\n")
|
||||
|
||||
expectParseError(t, "while (1) break x", "<stdin>: error: There is no containing label named \"x\"\n")
|
||||
expectParseError(t, "while (1) continue x", "<stdin>: error: There is no containing label named \"x\"\n")
|
||||
}
|
||||
|
||||
func TestArrow(t *testing.T) {
|
||||
expectParseError(t, "({a: b, c() {}}) => {}", "<stdin>: error: Invalid binding pattern\n")
|
||||
expectParseError(t, "({a: b, get c() {}}) => {}", "<stdin>: error: Invalid binding pattern\n")
|
||||
expectParseError(t, "({a: b, set c() {}}) => {}", "<stdin>: error: Invalid binding pattern\n")
|
||||
|
||||
expectPrinted(t, "x => function() {}", "(x) => function() {\n};\n")
|
||||
expectPrinted(t, "(x) => function() {}", "(x) => function() {\n};\n")
|
||||
expectPrinted(t, "(x => function() {})", "(x) => function() {\n};\n")
|
||||
}
|
||||
|
||||
func TestTemplate(t *testing.T) {
|
||||
expectPrinted(t, "`a${1 + `b${2}c` + 3}d`", "`a${1 + `b${2}c` + 3}d`;\n")
|
||||
}
|
||||
|
||||
func TestConstantFolding(t *testing.T) {
|
||||
expectPrinted(t, "!false", "true;\n")
|
||||
expectPrinted(t, "!true", "false;\n")
|
||||
|
||||
expectPrinted(t, "!!0", "false;\n")
|
||||
expectPrinted(t, "!!-0", "false;\n")
|
||||
expectPrinted(t, "!!1", "true;\n")
|
||||
expectPrinted(t, "!!NaN", "false;\n")
|
||||
expectPrinted(t, "!!Infinity", "true;\n")
|
||||
expectPrinted(t, "!!-Infinity", "true;\n")
|
||||
expectPrinted(t, "!!\"\"", "false;\n")
|
||||
expectPrinted(t, "!!\"x\"", "true;\n")
|
||||
expectPrinted(t, "!!function() {}", "true;\n")
|
||||
expectPrinted(t, "!!(() => {})", "true;\n")
|
||||
expectPrinted(t, "!!0n", "false;\n")
|
||||
expectPrinted(t, "!!1n", "true;\n")
|
||||
|
||||
expectPrinted(t, "1 ? 2 : 3", "2;\n")
|
||||
expectPrinted(t, "0 ? 1 : 2", "2;\n")
|
||||
expectPrinted(t, "1 && 2", "2;\n")
|
||||
expectPrinted(t, "1 || 2", "1;\n")
|
||||
expectPrinted(t, "0 && 1", "0;\n")
|
||||
expectPrinted(t, "0 || 1", "1;\n")
|
||||
|
||||
expectPrinted(t, "null ?? 1", "1;\n")
|
||||
expectPrinted(t, "undefined ?? 1", "1;\n")
|
||||
expectPrinted(t, "0 ?? 1", "0;\n")
|
||||
expectPrinted(t, "false ?? 1", "false;\n")
|
||||
expectPrinted(t, "\"\" ?? 1", "\"\";\n")
|
||||
|
||||
expectPrinted(t, "typeof undefined", "\"undefined\";\n")
|
||||
expectPrinted(t, "typeof null", "\"object\";\n")
|
||||
expectPrinted(t, "typeof false", "\"boolean\";\n")
|
||||
expectPrinted(t, "typeof true", "\"boolean\";\n")
|
||||
expectPrinted(t, "typeof 123", "\"number\";\n")
|
||||
expectPrinted(t, "typeof 123n", "\"bigint\";\n")
|
||||
expectPrinted(t, "typeof 'abc'", "\"string\";\n")
|
||||
expectPrinted(t, "typeof function() {}", "\"function\";\n")
|
||||
expectPrinted(t, "typeof (() => {})", "\"function\";\n")
|
||||
expectPrinted(t, "typeof {}", "typeof {};\n")
|
||||
expectPrinted(t, "typeof []", "typeof [];\n")
|
||||
|
||||
expectPrinted(t, "undefined === undefined", "true;\n")
|
||||
expectPrinted(t, "undefined !== undefined", "false;\n")
|
||||
expectPrinted(t, "undefined == undefined", "true;\n")
|
||||
expectPrinted(t, "undefined != undefined", "false;\n")
|
||||
|
||||
expectPrinted(t, "null === null", "true;\n")
|
||||
expectPrinted(t, "null !== null", "false;\n")
|
||||
expectPrinted(t, "null == null", "true;\n")
|
||||
expectPrinted(t, "null != null", "false;\n")
|
||||
|
||||
expectPrinted(t, "undefined === null", "void 0 === null;\n")
|
||||
expectPrinted(t, "undefined !== null", "void 0 !== null;\n")
|
||||
expectPrinted(t, "undefined == null", "void 0 == null;\n")
|
||||
expectPrinted(t, "undefined != null", "void 0 != null;\n")
|
||||
|
||||
expectPrinted(t, "true === true", "true;\n")
|
||||
expectPrinted(t, "true === false", "false;\n")
|
||||
expectPrinted(t, "true !== true", "false;\n")
|
||||
expectPrinted(t, "true !== false", "true;\n")
|
||||
expectPrinted(t, "true == true", "true;\n")
|
||||
expectPrinted(t, "true == false", "false;\n")
|
||||
expectPrinted(t, "true != true", "false;\n")
|
||||
expectPrinted(t, "true != false", "true;\n")
|
||||
|
||||
expectPrinted(t, "1 === 1", "true;\n")
|
||||
expectPrinted(t, "1 === 2", "false;\n")
|
||||
expectPrinted(t, "1 === '1'", "1 === \"1\";\n")
|
||||
expectPrinted(t, "1 == 1", "true;\n")
|
||||
expectPrinted(t, "1 == 2", "false;\n")
|
||||
expectPrinted(t, "1 == '1'", "1 == \"1\";\n")
|
||||
|
||||
expectPrinted(t, "1 !== 1", "false;\n")
|
||||
expectPrinted(t, "1 !== 2", "true;\n")
|
||||
expectPrinted(t, "1 !== '1'", "1 !== \"1\";\n")
|
||||
expectPrinted(t, "1 != 1", "false;\n")
|
||||
expectPrinted(t, "1 != 2", "true;\n")
|
||||
expectPrinted(t, "1 != '1'", "1 != \"1\";\n")
|
||||
|
||||
expectParseError(t, "x === -0", "<stdin>: warning: Comparison with -0 using the === operator will also match 0\n")
|
||||
expectParseError(t, "x == -0", "<stdin>: warning: Comparison with -0 using the == operator will also match 0\n")
|
||||
expectParseError(t, "x !== -0", "<stdin>: warning: Comparison with -0 using the !== operator will also match 0\n")
|
||||
expectParseError(t, "x != -0", "<stdin>: warning: Comparison with -0 using the != operator will also match 0\n")
|
||||
expectParseError(t, "-0 === x", "<stdin>: warning: Comparison with -0 using the === operator will also match 0\n")
|
||||
expectParseError(t, "-0 == x", "<stdin>: warning: Comparison with -0 using the == operator will also match 0\n")
|
||||
expectParseError(t, "-0 !== x", "<stdin>: warning: Comparison with -0 using the !== operator will also match 0\n")
|
||||
expectParseError(t, "-0 != x", "<stdin>: warning: Comparison with -0 using the != operator will also match 0\n")
|
||||
|
||||
expectPrinted(t, "'a' === '\\x61'", "true;\n")
|
||||
expectPrinted(t, "'a' === '\\x62'", "false;\n")
|
||||
expectPrinted(t, "'a' === 'abc'", "false;\n")
|
||||
expectPrinted(t, "'a' !== '\\x61'", "false;\n")
|
||||
expectPrinted(t, "'a' !== '\\x62'", "true;\n")
|
||||
expectPrinted(t, "'a' !== 'abc'", "true;\n")
|
||||
expectPrinted(t, "'a' == '\\x61'", "true;\n")
|
||||
expectPrinted(t, "'a' == '\\x62'", "false;\n")
|
||||
expectPrinted(t, "'a' == 'abc'", "false;\n")
|
||||
expectPrinted(t, "'a' != '\\x61'", "false;\n")
|
||||
expectPrinted(t, "'a' != '\\x62'", "true;\n")
|
||||
expectPrinted(t, "'a' != 'abc'", "true;\n")
|
||||
|
||||
expectPrinted(t, "'a' + 'b'", "\"ab\";\n")
|
||||
expectPrinted(t, "'a' + 'bc'", "\"abc\";\n")
|
||||
expectPrinted(t, "'ab' + 'c'", "\"abc\";\n")
|
||||
expectPrinted(t, "x + 'a' + 'b'", "x + \"ab\";\n")
|
||||
expectPrinted(t, "x + 'a' + 'bc'", "x + \"abc\";\n")
|
||||
expectPrinted(t, "x + 'ab' + 'c'", "x + \"abc\";\n")
|
||||
expectPrinted(t, "'a' + 1", "\"a\" + 1;\n")
|
||||
|
||||
expectPrinted(t, "'string' + `template`", "`stringtemplate`;\n")
|
||||
expectPrinted(t, "'string' + `a${foo}b`", "`stringa${foo}b`;\n")
|
||||
expectPrinted(t, "'string' + tag`template`", "\"string\" + tag`template`;\n")
|
||||
expectPrinted(t, "`template` + 'string'", "`templatestring`;\n")
|
||||
expectPrinted(t, "`a${foo}b` + 'string'", "`a${foo}bstring`;\n")
|
||||
expectPrinted(t, "tag`template` + 'string'", "tag`template` + \"string\";\n")
|
||||
expectPrinted(t, "`template` + `a${foo}b`", "`templatea${foo}b`;\n")
|
||||
expectPrinted(t, "`a${foo}b` + `template`", "`a${foo}btemplate`;\n")
|
||||
expectPrinted(t, "`a${foo}b` + `x${bar}y`", "`a${foo}bx${bar}y`;\n")
|
||||
expectPrinted(t, "`a${i}${j}bb` + `xxx${bar}yyyy`", "`a${i}${j}bbxxx${bar}yyyy`;\n")
|
||||
expectPrinted(t, "`a${foo}bb` + `xxx${i}${j}yyyy`", "`a${foo}bbxxx${i}${j}yyyy`;\n")
|
||||
expectPrinted(t, "`template` + tag`template2`", "`template` + tag`template2`;\n")
|
||||
expectPrinted(t, "tag`template` + `template2`", "tag`template` + `template2`;\n")
|
||||
|
||||
expectPrinted(t, "123", "123;\n")
|
||||
expectPrinted(t, "123 .toString()", "123 .toString();\n")
|
||||
expectPrinted(t, "-123", "-123;\n")
|
||||
expectPrinted(t, "(-123).toString()", "(-123).toString();\n")
|
||||
expectPrinted(t, "-0", "-0;\n")
|
||||
expectPrinted(t, "(-0).toString()", "(-0).toString();\n")
|
||||
expectPrinted(t, "-0 === 0", "true;\n")
|
||||
|
||||
expectPrinted(t, "NaN", "NaN;\n")
|
||||
expectPrinted(t, "NaN.toString()", "NaN.toString();\n")
|
||||
expectPrinted(t, "NaN === NaN", "false;\n")
|
||||
|
||||
expectPrinted(t, "Infinity", "Infinity;\n")
|
||||
expectPrinted(t, "Infinity.toString()", "Infinity.toString();\n")
|
||||
expectPrinted(t, "(-Infinity).toString()", "(-Infinity).toString();\n")
|
||||
expectPrinted(t, "Infinity === Infinity", "true;\n")
|
||||
expectPrinted(t, "Infinity === -Infinity", "false;\n")
|
||||
|
||||
expectPrinted(t, "123n === 1_2_3n", "true;\n")
|
||||
}
|
||||
|
||||
func TestImport(t *testing.T) {
|
||||
expectPrinted(t, "import \"foo\"", "import \"foo\";\n")
|
||||
expectPrinted(t, "import {} from \"foo\"", "import {} from \"foo\";\n")
|
||||
expectPrinted(t, "import {x} from \"foo\";x", "import {x} from \"foo\";\nx;\n")
|
||||
expectPrinted(t, "import {x as y} from \"foo\";y", "import {x as y} from \"foo\";\ny;\n")
|
||||
expectPrinted(t, "import {x as y, z} from \"foo\";y;z", "import {x as y, z} from \"foo\";\ny;\nz;\n")
|
||||
expectPrinted(t, "import {x as y, z,} from \"foo\";y;z", "import {x as y, z} from \"foo\";\ny;\nz;\n")
|
||||
expectPrinted(t, "import z, {x as y} from \"foo\";y;z", "import z, {x as y} from \"foo\";\ny;\nz;\n")
|
||||
expectPrinted(t, "import z from \"foo\";z", "import z from \"foo\";\nz;\n")
|
||||
expectPrinted(t, "import * as ns from \"foo\";ns;ns.x", "import * as ns from \"foo\";\nns;\nns.x;\n")
|
||||
expectPrinted(t, "import z, * as ns from \"foo\";z;ns;ns.x", "import z, * as ns from \"foo\";\nz;\nns;\nns.x;\n")
|
||||
|
||||
expectParseError(t, "import * from \"foo\"", "<stdin>: error: Expected \"as\" but found \"from\"\n")
|
||||
|
||||
expectPrinted(t, "import('foo')", "import(\"foo\");\n")
|
||||
expectPrinted(t, "(import('foo'))", "import(\"foo\");\n")
|
||||
expectPrinted(t, "import('foo').then(() => {})", "import(\"foo\").then(() => {\n});\n")
|
||||
expectParseError(t, "import()", "<stdin>: error: Unexpected \")\"\n")
|
||||
expectParseError(t, "import(...a)", "<stdin>: error: Unexpected \"...\"\n")
|
||||
expectParseError(t, "import(a, b)", "<stdin>: error: Expected \")\" but found \",\"\n")
|
||||
|
||||
expectPrinted(t, "import.meta", "import.meta;\n")
|
||||
expectPrinted(t, "(import.meta)", "import.meta;\n")
|
||||
}
|
||||
|
||||
func TestExport(t *testing.T) {
|
||||
expectPrinted(t, "export default x", "export default x;\n")
|
||||
expectPrinted(t, "export class x {}", "export class x {\n}\n")
|
||||
expectPrinted(t, "export function x() {}", "export function x() {\n}\n")
|
||||
expectPrinted(t, "export async function x() {}", "export async function x() {\n}\n")
|
||||
expectPrinted(t, "export var x, y", "export var x, y;\n")
|
||||
expectPrinted(t, "export let x, y", "export let x, y;\n")
|
||||
expectPrinted(t, "export const x = 0, y = 1", "export const x = 0, y = 1;\n")
|
||||
expectPrinted(t, "export * from \"foo\"", "export * from \"foo\";\n")
|
||||
expectPrinted(t, "export * as ns from \"foo\"", "export * as ns from \"foo\";\n")
|
||||
expectPrinted(t, "export {x}", "export {x};\n")
|
||||
expectPrinted(t, "export {x as y}", "export {x as y};\n")
|
||||
expectPrinted(t, "export {x as y, z}", "export {x as y, z};\n")
|
||||
expectPrinted(t, "export {x as y, z,}", "export {x as y, z};\n")
|
||||
expectPrinted(t, "export {x} from \"foo\"", "export {x} from \"foo\";\n")
|
||||
expectPrinted(t, "export {x as y} from \"foo\"", "export {x as y} from \"foo\";\n")
|
||||
expectPrinted(t, "export {x as y, z} from \"foo\"", "export {x as y, z} from \"foo\";\n")
|
||||
expectPrinted(t, "export {x as y, z,} from \"foo\"", "export {x as y, z} from \"foo\";\n")
|
||||
|
||||
expectParseError(t, "export x from \"foo\"", "<stdin>: error: Unexpected \"x\"\n")
|
||||
expectParseError(t, "export async", "<stdin>: error: Expected \"function\" but found end of file\n")
|
||||
expectParseError(t, "export async function", "<stdin>: error: Expected identifier but found end of file\n")
|
||||
expectParseError(t, "export async () => {}", "<stdin>: error: Expected \"function\" but found \"(\"\n")
|
||||
}
|
||||
|
||||
func TestExportDuplicates(t *testing.T) {
|
||||
expectPrinted(t, "export {x}", "export {x};\n")
|
||||
expectPrinted(t, "export {x, x as y}", "export {x, x as y};\n")
|
||||
expectPrinted(t, "export {x};export {x as y} from 'foo'", "export {x};\nexport {x as y} from \"foo\";\n")
|
||||
expectPrinted(t, "export {x};export default function x() {}", "export {x};\nexport default function x() {\n}\n")
|
||||
expectPrinted(t, "export {x};export default class x {}", "export {x};\nexport default class x {\n}\n")
|
||||
|
||||
expectParseError(t, "export {x, x}", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x, y as x}", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export function x() {}", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export class x {}", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export const x = 0", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export let x", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export var x", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export {x} from 'foo'", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export {y as x} from 'foo'", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x};export * as x from 'foo'", "<stdin>: error: Multiple exports with the same name \"x\"\n")
|
||||
expectParseError(t, "export {x as default};export default 0", "<stdin>: error: Multiple exports with the same name \"default\"\n")
|
||||
expectParseError(t, "export {x as default};export default function() {}", "<stdin>: error: Multiple exports with the same name \"default\"\n")
|
||||
expectParseError(t, "export {x as default};export default class {}", "<stdin>: error: Multiple exports with the same name \"default\"\n")
|
||||
expectParseError(t, "export {x as default};export default function x() {}", "<stdin>: error: Multiple exports with the same name \"default\"\n")
|
||||
expectParseError(t, "export {x as default};export default class x {}", "<stdin>: error: Multiple exports with the same name \"default\"\n")
|
||||
}
|
||||
|
||||
func TestExportDefault(t *testing.T) {
|
||||
expectParseError(t, "export default 1, 2", "<stdin>: error: Expected \";\" but found \",\"\n")
|
||||
expectPrinted(t, "export default (1, 2)", "export default (1, 2);\n")
|
||||
|
||||
// This is a corner case in the ES6 grammar. The "export default" statement
|
||||
// normally takes an expression except for the function and class keywords
|
||||
// which behave sort of like their respective declarations instead.
|
||||
expectPrinted(t, "export default function() {} - after", "export default function() {\n}\n-after;\n")
|
||||
expectPrinted(t, "export default function*() {} - after", "export default function* () {\n}\n-after;\n")
|
||||
expectPrinted(t, "export default function foo() {} - after", "export default function foo() {\n}\n-after;\n")
|
||||
expectPrinted(t, "export default function* foo() {} - after", "export default function* foo() {\n}\n-after;\n")
|
||||
expectPrinted(t, "export default class {} - after", "export default class {\n}\n-after;\n")
|
||||
expectPrinted(t, "export default class Foo {} - after", "export default class Foo {\n}\n-after;\n")
|
||||
}
|
||||
|
||||
func TestExportClause(t *testing.T) {
|
||||
expectPrinted(t, "export {x, y}", "export {x, y};\n")
|
||||
expectPrinted(t, "export {x, y as z,}", "export {x, y as z};\n")
|
||||
expectPrinted(t, "export {x, y} from 'path'", "export {x, y} from \"path\";\n")
|
||||
expectPrinted(t, "export {default, if} from 'path'", "export {default, if} from \"path\";\n")
|
||||
expectPrinted(t, "export {default as foo, if as bar} from 'path'", "export {default as foo, if as bar} from \"path\";\n")
|
||||
expectParseError(t, "export {default}", "<stdin>: error: Expected identifier but found \"default\"\n")
|
||||
expectParseError(t, "export {default as foo}", "<stdin>: error: Expected identifier but found \"default\"\n")
|
||||
expectParseError(t, "export {if}", "<stdin>: error: Expected identifier but found \"if\"\n")
|
||||
expectParseError(t, "export {if as foo}", "<stdin>: error: Expected identifier but found \"if\"\n")
|
||||
}
|
||||
|
||||
func TestCatch(t *testing.T) {
|
||||
expectPrinted(t, "try {} catch (e) {}", "try {\n} catch (e) {\n}\n")
|
||||
expectPrinted(t, "try {} catch (e) { var e }", "try {\n} catch (e) {\n var e;\n}\n")
|
||||
expectPrinted(t, "try {} catch (e) { function e() {} }", "try {\n} catch (e) {\n function e() {\n }\n}\n")
|
||||
expectPrinted(t, "var e; try {} catch (e) {}", "var e;\ntry {\n} catch (e) {\n}\n")
|
||||
expectPrinted(t, "let e; try {} catch (e) {}", "let e;\ntry {\n} catch (e) {\n}\n")
|
||||
|
||||
expectParseError(t, "try {} catch ({e}) { var e }", "<stdin>: error: \"e\" has already been declared\n")
|
||||
expectParseError(t, "try {} catch ({e}) { function e() {} }", "<stdin>: error: \"e\" has already been declared\n")
|
||||
expectParseError(t, "try {} catch (e) { let e }", "<stdin>: error: \"e\" has already been declared\n")
|
||||
expectParseError(t, "try {} catch (e) { const e = 0 }", "<stdin>: error: \"e\" has already been declared\n")
|
||||
}
|
||||
|
||||
func TestMangleFor(t *testing.T) {
|
||||
expectPrintedMangle(t, "var a; while (1) ;", "for (var a; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "let a; while (1) ;", "let a;\nfor (; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "const a=0; while (1) ;", "const a = 0;\nfor (; ; )\n ;\n")
|
||||
|
||||
expectPrintedMangle(t, "var a; for (var b;;) ;", "for (var a, b; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "let a; for (let b;;) ;", "let a;\nfor (let b; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "const a=0; for (const b = 1;;) ;", "const a = 0;\nfor (const b = 1; ; )\n ;\n")
|
||||
|
||||
expectPrintedMangle(t, "export var a; while (1) ;", "export var a;\nfor (; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "export let a; while (1) ;", "export let a;\nfor (; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "export const a=0; while (1) ;", "export const a = 0;\nfor (; ; )\n ;\n")
|
||||
|
||||
expectPrintedMangle(t, "export var a; for (var b;;) ;", "export var a;\nfor (var b; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "export let a; for (let b;;) ;", "export let a;\nfor (let b; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "export const a=0; for (const b = 1;;) ;", "export const a = 0;\nfor (const b = 1; ; )\n ;\n")
|
||||
|
||||
expectPrintedMangle(t, "var a; for (let b;;) ;", "var a;\nfor (let b; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "let a; for (const b=0;;) ;", "let a;\nfor (const b = 0; ; )\n ;\n")
|
||||
expectPrintedMangle(t, "const a=0; for (var b;;) ;", "const a = 0;\nfor (var b; ; )\n ;\n")
|
||||
|
||||
expectPrintedMangle(t, "a(); while (1) ;", "for (a(); ; )\n ;\n")
|
||||
expectPrintedMangle(t, "a(); for (b();;) ;", "for (a(), b(); ; )\n ;\n")
|
||||
}
|
||||
|
||||
func TestMangleUndefined(t *testing.T) {
|
||||
expectPrintedMangle(t, "const x = undefined", "const x = void 0;\n")
|
||||
expectPrintedMangle(t, "let x = undefined", "let x;\n")
|
||||
expectPrintedMangle(t, "var x = undefined", "var x;\n")
|
||||
expectPrintedMangle(t, "function foo(a) { if (!a) return undefined; a() }", "function foo(a) {\n if (!a)\n return;\n a();\n}\n")
|
||||
}
|
||||
|
||||
func TestMangleIf(t *testing.T) {
|
||||
expectPrintedMangle(t, "1 ? a() : b()", "a();\n")
|
||||
expectPrintedMangle(t, "0 ? a() : b()", "b();\n")
|
||||
expectPrintedMangle(t, "a ? b() : c()", "a ? b() : c();\n")
|
||||
expectPrintedMangle(t, "!a ? b() : c()", "a ? c() : b();\n")
|
||||
expectPrintedMangle(t, "!!a ? b() : c()", "a ? b() : c();\n")
|
||||
expectPrintedMangle(t, "!!!a ? b() : c()", "a ? c() : b();\n")
|
||||
|
||||
expectPrintedMangle(t, "if (1) a(); else b()", "a();\n")
|
||||
expectPrintedMangle(t, "if (0) a(); else b()", "b();\n")
|
||||
expectPrintedMangle(t, "if (a) b(); else c()", "a ? b() : c();\n")
|
||||
expectPrintedMangle(t, "if (!a) b(); else c()", "a ? c() : b();\n")
|
||||
expectPrintedMangle(t, "if (!!a) b(); else c()", "a ? b() : c();\n")
|
||||
expectPrintedMangle(t, "if (!!!a) b(); else c()", "a ? c() : b();\n")
|
||||
|
||||
expectPrintedMangle(t, "if (1) a()", "a();\n")
|
||||
expectPrintedMangle(t, "if (0) a()", "")
|
||||
expectPrintedMangle(t, "if (a) b()", "a && b();\n")
|
||||
expectPrintedMangle(t, "if (!a) b()", "a || b();\n")
|
||||
expectPrintedMangle(t, "if (!!a) b()", "a && b();\n")
|
||||
expectPrintedMangle(t, "if (!!!a) b()", "a || b();\n")
|
||||
|
||||
expectPrintedMangle(t, "if (1) {} else a()", "")
|
||||
expectPrintedMangle(t, "if (0) {} else a()", "a();\n")
|
||||
expectPrintedMangle(t, "if (a) {} else b()", "a || b();\n")
|
||||
expectPrintedMangle(t, "if (!a) {} else b()", "a && b();\n")
|
||||
expectPrintedMangle(t, "if (!!a) {} else b()", "a || b();\n")
|
||||
expectPrintedMangle(t, "if (!!!a) {} else b()", "a && b();\n")
|
||||
|
||||
expectPrintedMangle(t, "if (a) {} else throw b", "if (!a)\n throw b;\n")
|
||||
expectPrintedMangle(t, "if (!a) {} else throw b", "if (a)\n throw b;\n")
|
||||
}
|
||||
|
||||
func TestTrimCodeInDeadControlFlow(t *testing.T) {
|
||||
expectPrintedMangle(t, "if (1) a(); else { ; }", "a();\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { b() }", "a();\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { const b = c }", "a();\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { let b }", "a();\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { throw b }", "a();\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { return b }", "a();\n")
|
||||
expectPrintedMangle(t, "b: { if (1) a(); else { break b } }", "b:\n a();\n")
|
||||
expectPrintedMangle(t, "b: { if (1) a(); else { continue b } }", "b:\n a();\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { class b {} }", "a();\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { debugger }", "a();\n")
|
||||
|
||||
expectPrintedMangle(t, "if (0) {let a = 1} else a()", "a();\n")
|
||||
expectPrintedMangle(t, "if (1) {let a = 1} else a()", "{\n let a = 1;\n}\n")
|
||||
expectPrintedMangle(t, "if (0) a(); else {let a = 1}", "{\n let a = 1;\n}\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else {let a = 1}", "a();\n")
|
||||
|
||||
expectPrintedMangle(t, "if (1) a(); else { var a = b }", "if (1)\n a();\nelse\n var a;\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { var [a] = b }", "if (1)\n a();\nelse\n var a;\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { var {x: a} = b }", "if (1)\n a();\nelse\n var a;\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { function a() {} }", "if (1)\n a();\nelse\n function a() {\n }\n")
|
||||
expectPrintedMangle(t, "if (1) a(); else { for(;;){var a} }", "if (1)\n a();\nelse\n for (; ; )\n var a;\n")
|
||||
expectPrintedMangle(t, "if (1) { a(); b() } else { var a; var b; }", "if (1)\n a(), b();\nelse\n var a, b;\n")
|
||||
}
|
||||
|
||||
// Make sure we can handle the unicode replacement character "�" in various places
|
||||
func TestReplacementCharacter(t *testing.T) {
|
||||
expectPrinted(t, "//\uFFFD\n123", "123;\n")
|
||||
expectPrinted(t, "/*\uFFFD*/123", "123;\n")
|
||||
|
||||
expectPrinted(t, "'\uFFFD'", "\"\\uFFFD\";\n")
|
||||
expectPrinted(t, "\"\uFFFD\"", "\"\\uFFFD\";\n")
|
||||
expectPrinted(t, "`\uFFFD`", "`\\uFFFD`;\n")
|
||||
expectPrinted(t, "/\uFFFD/", "/\uFFFD/;\n")
|
||||
|
||||
expectPrintedJSX(t, "<a>\uFFFD</a>", "React.createElement(\"a\", null, \"\\uFFFD\");\n")
|
||||
}
|
||||
|
||||
func TestNewTarget(t *testing.T) {
|
||||
expectPrinted(t, "new.target", "new.target;\n")
|
||||
expectPrinted(t, "(new.target)", "new.target;\n")
|
||||
}
|
||||
|
||||
func TestJSX(t *testing.T) {
|
||||
expectParseError(t, "<a/>", "<stdin>: error: Unexpected \"<\"\n")
|
||||
|
||||
expectPrintedJSX(t, "<a/>", "React.createElement(\"a\", null);\n")
|
||||
expectPrintedJSX(t, "<a></a>", "React.createElement(\"a\", null);\n")
|
||||
expectPrintedJSX(t, "<A/>", "React.createElement(A, null);\n")
|
||||
expectPrintedJSX(t, "<a.b/>", "React.createElement(a.b, null);\n")
|
||||
expectPrintedJSX(t, "<_a/>", "React.createElement(_a, null);\n")
|
||||
expectPrintedJSX(t, "<a-b/>", "React.createElement(\"a-b\", null);\n")
|
||||
|
||||
expectPrintedJSX(t, "<a b/>", "React.createElement(\"a\", {\n b: true\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"\\\"/>", "React.createElement(\"a\", {\n b: \"\\\\\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"<>\"/>", "React.createElement(\"a\", {\n b: \"<>\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"<>\"/>", "React.createElement(\"a\", {\n b: \"<>\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"&wrong;\"/>", "React.createElement(\"a\", {\n b: \"&wrong;\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b={1, 2}/>", "React.createElement(\"a\", {\n b: (1, 2)\n});\n")
|
||||
expectPrintedJSX(t, "<a b={<c/>}/>", "React.createElement(\"a\", {\n b: React.createElement(\"c\", null)\n});\n")
|
||||
expectPrintedJSX(t, "<a {...props}/>", "React.createElement(\"a\", {\n ...props\n});\n")
|
||||
|
||||
expectPrintedJSX(t, "<a>\n</a>", "React.createElement(\"a\", null);\n")
|
||||
expectPrintedJSX(t, "<a>123</a>", "React.createElement(\"a\", null, \"123\");\n")
|
||||
expectPrintedJSX(t, "<a>}</a>", "React.createElement(\"a\", null, \"}\");\n")
|
||||
expectPrintedJSX(t, "<a>=</a>", "React.createElement(\"a\", null, \"=\");\n")
|
||||
expectPrintedJSX(t, "<a>></a>", "React.createElement(\"a\", null, \">\");\n")
|
||||
expectPrintedJSX(t, "<a>>=</a>", "React.createElement(\"a\", null, \">=\");\n")
|
||||
expectPrintedJSX(t, "<a>>></a>", "React.createElement(\"a\", null, \">>\");\n")
|
||||
expectPrintedJSX(t, "<a>{}</a>", "React.createElement(\"a\", null);\n")
|
||||
expectPrintedJSX(t, "<a>{/* comment */}</a>", "React.createElement(\"a\", null);\n")
|
||||
expectPrintedJSX(t, "<a>{1, 2}</a>", "React.createElement(\"a\", null, (1, 2));\n")
|
||||
expectPrintedJSX(t, "<a><></a>", "React.createElement(\"a\", null, \"<>\");\n")
|
||||
expectPrintedJSX(t, "<a>&wrong;</a>", "React.createElement(\"a\", null, \"&wrong;\");\n")
|
||||
|
||||
// Note: The TypeScript compiler and Babel disagree. This matches TypeScript.
|
||||
expectPrintedJSX(t, "<a b=\" c\"/>", "React.createElement(\"a\", {\n b: \" c\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\" \nc\"/>", "React.createElement(\"a\", {\n b: \" \\nc\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"\n c\"/>", "React.createElement(\"a\", {\n b: \"\\n c\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"c \"/>", "React.createElement(\"a\", {\n b: \"c \"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"c \n\"/>", "React.createElement(\"a\", {\n b: \"c \\n\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"c\n \"/>", "React.createElement(\"a\", {\n b: \"c\\n \"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"c d\"/>", "React.createElement(\"a\", {\n b: \"c d\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"c \nd\"/>", "React.createElement(\"a\", {\n b: \"c \\nd\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"c\n d\"/>", "React.createElement(\"a\", {\n b: \"c\\n d\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\" c\"/>", "React.createElement(\"a\", {\n b: \" c\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\" \nc\"/>", "React.createElement(\"a\", {\n b: \" \\nc\"\n});\n")
|
||||
expectPrintedJSX(t, "<a b=\"\n c\"/>", "React.createElement(\"a\", {\n b: \"\\n c\"\n});\n")
|
||||
|
||||
expectPrintedJSX(t, "<a> b</a>", "React.createElement(\"a\", null, \" b\");\n")
|
||||
expectPrintedJSX(t, "<a> \nb</a>", "React.createElement(\"a\", null, \"b\");\n")
|
||||
expectPrintedJSX(t, "<a>\n b</a>", "React.createElement(\"a\", null, \"b\");\n")
|
||||
expectPrintedJSX(t, "<a>b </a>", "React.createElement(\"a\", null, \"b \");\n")
|
||||
expectPrintedJSX(t, "<a>b \n</a>", "React.createElement(\"a\", null, \"b\");\n")
|
||||
expectPrintedJSX(t, "<a>b\n </a>", "React.createElement(\"a\", null, \"b\");\n")
|
||||
expectPrintedJSX(t, "<a>b c</a>", "React.createElement(\"a\", null, \"b c\");\n")
|
||||
expectPrintedJSX(t, "<a>b \nc</a>", "React.createElement(\"a\", null, \"b c\");\n")
|
||||
expectPrintedJSX(t, "<a>b\n c</a>", "React.createElement(\"a\", null, \"b c\");\n")
|
||||
expectPrintedJSX(t, "<a> b</a>", "React.createElement(\"a\", null, \" b\");\n")
|
||||
expectPrintedJSX(t, "<a> \nb</a>", "React.createElement(\"a\", null, \"b\");\n")
|
||||
expectPrintedJSX(t, "<a>\n b</a>", "React.createElement(\"a\", null, \"b\");\n")
|
||||
|
||||
expectParseErrorJSX(t, "<a b=true/>", "<stdin>: error: Expected \"{\" but found \"true\"\n")
|
||||
expectParseErrorJSX(t, "</a>", "<stdin>: error: Expected identifier but found \"/\"\n")
|
||||
expectParseErrorJSX(t, "<a></b>", "<stdin>: error: Expected closing tag \"b\" to match opening tag \"a\"\n")
|
||||
expectParseErrorJSX(t, "<\na\n.\nb\n>\n<\n/\nc\n.\nd\n>", "<stdin>: error: Expected closing tag \"c.d\" to match opening tag \"a.b\"\n")
|
||||
expectParseErrorJSX(t, "<a-b.c>", "<stdin>: error: Expected \">\" but found \".\"\n")
|
||||
expectParseErrorJSX(t, "<a.b-c>", "<stdin>: error: Unexpected \"-\"\n")
|
||||
expectParseErrorJSX(t, "<a:b>", "<stdin>: error: Expected \">\" but found \":\"\n")
|
||||
expectParseErrorJSX(t, "<a>{...children}</a>", "<stdin>: error: Unexpected \"...\"\n")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,321 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"esbuild/logging"
|
||||
"esbuild/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertEqual(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Fatalf("%s != %s", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func expectPrintedCommon(t *testing.T, name string, contents string, expected string, options Options) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
log, join := logging.NewDeferLog()
|
||||
ast, ok := parser.Parse(log, logging.Source{
|
||||
Index: 0,
|
||||
AbsolutePath: "<stdin>",
|
||||
PrettyPath: "<stdin>",
|
||||
Contents: contents,
|
||||
}, parser.ParseOptions{})
|
||||
msgs := join()
|
||||
text := ""
|
||||
for _, msg := range msgs {
|
||||
text += msg.String(logging.StderrOptions{}, logging.TerminalInfo{})
|
||||
}
|
||||
assertEqual(t, text, "")
|
||||
if !ok {
|
||||
t.Fatal("Parse error")
|
||||
}
|
||||
js, _ := Print(ast, options)
|
||||
assertEqual(t, string(js), expected)
|
||||
})
|
||||
}
|
||||
|
||||
func expectPrinted(t *testing.T, contents string, expected string) {
|
||||
expectPrintedCommon(t, contents, contents, expected, Options{})
|
||||
}
|
||||
|
||||
func expectPrintedMinify(t *testing.T, contents string, expected string) {
|
||||
expectPrintedCommon(t, contents+" [minified]", contents, expected, Options{
|
||||
RemoveWhitespace: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestArray(t *testing.T) {
|
||||
expectPrinted(t, "[]", "[];\n")
|
||||
expectPrinted(t, "[,]", "[,];\n")
|
||||
expectPrinted(t, "[,,]", "[, ,];\n")
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
expectPrinted(t, "new x", "new x();\n")
|
||||
expectPrinted(t, "new x()", "new x();\n")
|
||||
expectPrinted(t, "new (x)", "new x();\n")
|
||||
expectPrinted(t, "new (x())", "new (x())();\n")
|
||||
expectPrinted(t, "new (new x())", "new new x()();\n")
|
||||
expectPrinted(t, "new (x + x)", "new (x + x)();\n")
|
||||
expectPrinted(t, "(new x)()", "new x()();\n")
|
||||
}
|
||||
|
||||
func TestCall(t *testing.T) {
|
||||
expectPrinted(t, "x()()()", "x()()();\n")
|
||||
expectPrinted(t, "x().y()[z]()", "x().y()[z]();\n")
|
||||
expectPrinted(t, "(--x)();", "(--x)();\n")
|
||||
expectPrinted(t, "(x--)();", "(x--)();\n")
|
||||
}
|
||||
|
||||
func TestMember(t *testing.T) {
|
||||
expectPrinted(t, "x.y[z]", "x.y[z];\n")
|
||||
expectPrinted(t, "((x+1).y+1)[z]", "((x + 1).y + 1)[z];\n")
|
||||
}
|
||||
|
||||
func TestComma(t *testing.T) {
|
||||
expectPrinted(t, "1, 2, 3", "1, 2, 3;\n")
|
||||
expectPrinted(t, "(1, 2), 3", "1, 2, 3;\n")
|
||||
expectPrinted(t, "1, (2, 3)", "1, 2, 3;\n")
|
||||
expectPrinted(t, "a ? (b, c) : (d, e)", "a ? (b, c) : (d, e);\n")
|
||||
expectPrinted(t, "let x = (a, b)", "let x = (a, b);\n")
|
||||
expectPrinted(t, "(x = a), b", "x = a, b;\n")
|
||||
expectPrinted(t, "x = (a, b)", "x = (a, b);\n")
|
||||
expectPrinted(t, "x((1, 2))", "x((1, 2));\n")
|
||||
}
|
||||
|
||||
func TestUnary(t *testing.T) {
|
||||
expectPrinted(t, "+(x--)", "+x--;\n")
|
||||
expectPrinted(t, "-(x++)", "-x++;\n")
|
||||
}
|
||||
|
||||
func TestNullish(t *testing.T) {
|
||||
// "??" can't directly contain "||" or "&&"
|
||||
expectPrinted(t, "(a && b) ?? c", "(a && b) ?? c;\n")
|
||||
expectPrinted(t, "(a || b) ?? c", "(a || b) ?? c;\n")
|
||||
expectPrinted(t, "a ?? (b && c)", "a ?? (b && c);\n")
|
||||
expectPrinted(t, "a ?? (b || c)", "a ?? (b || c);\n")
|
||||
|
||||
// "||" and "&&" can't directly contain "??"
|
||||
expectPrinted(t, "a && (b ?? c)", "a && (b ?? c);\n")
|
||||
expectPrinted(t, "a || (b ?? c)", "a || (b ?? c);\n")
|
||||
expectPrinted(t, "(a ?? b) && c", "(a ?? b) && c;\n")
|
||||
expectPrinted(t, "(a ?? b) || c", "(a ?? b) || c;\n")
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
expectPrinted(t, "let x = ''", "let x = \"\";\n")
|
||||
expectPrinted(t, "let x = '\b'", "let x = \"\\b\";\n")
|
||||
expectPrinted(t, "let x = '\f'", "let x = \"\\f\";\n")
|
||||
expectPrinted(t, "let x = '\t'", "let x = \"\t\";\n")
|
||||
expectPrinted(t, "let x = '\v'", "let x = \"\\v\";\n")
|
||||
expectPrinted(t, "let x = '\\n'", "let x = \"\\n\";\n")
|
||||
expectPrinted(t, "let x = '\\''", "let x = \"'\";\n")
|
||||
expectPrinted(t, "let x = '\\\"'", "let x = '\"';\n")
|
||||
expectPrinted(t, "let x = '\\'\"'", "let x = `'\"`;\n")
|
||||
expectPrinted(t, "let x = '\\\\'", "let x = \"\\\\\";\n")
|
||||
expectPrinted(t, "let x = '\x00'", "let x = \"\\x00\";\n")
|
||||
expectPrinted(t, "let x = '\x10'", "let x = \"\\x10\";\n")
|
||||
expectPrinted(t, "let x = '\uABCD'", "let x = \"\\uABCD\";\n")
|
||||
expectPrinted(t, "let x = '\U000123AB'", "let x = \"\\uD808\\uDFAB\";\n")
|
||||
|
||||
expectPrinted(t, "let x = '\\x80'", "let x = \"\\x80\";\n")
|
||||
expectPrinted(t, "let x = '\\xFF'", "let x = \"\\xFF\";\n")
|
||||
expectPrinted(t, "let x = '\\xF0\\x9F\\x8D\\x95'", "let x = \"\\xF0\\x9F\\x8D\\x95\";\n")
|
||||
expectPrinted(t, "let x = '\\uD801\\uDC02\\uDC03\\uD804'", "let x = \"\\uD801\\uDC02\\uDC03\\uD804\";\n")
|
||||
}
|
||||
|
||||
func TestObject(t *testing.T) {
|
||||
expectPrinted(t, "let x = {'(':')'}", "let x = {\n \"(\": \")\"\n};\n")
|
||||
}
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
expectPrinted(t,
|
||||
"function foo(a = (b, c), ...d) {}",
|
||||
"function foo(a = (b, c), ...d) {\n}\n")
|
||||
expectPrinted(t,
|
||||
"function foo({[1 + 2]: a = 3} = {[1 + 2]: 3}) {}",
|
||||
"function foo({[1 + 2]: a = 3} = {\n [1 + 2]: 3\n}) {\n}\n")
|
||||
expectPrinted(t,
|
||||
"function foo([a = (1, 2), ...[b, ...c]] = [1, [2, 3]]) {}",
|
||||
"function foo([a = (1, 2), ...[b, ...c]] = [1, [2, 3]]) {\n}\n")
|
||||
expectPrinted(t,
|
||||
"function foo([] = []) {}",
|
||||
"function foo([] = []) {\n}\n")
|
||||
expectPrinted(t,
|
||||
"function foo([,] = [,]) {}",
|
||||
"function foo([,] = [,]) {\n}\n")
|
||||
expectPrinted(t,
|
||||
"function foo([,,] = [,,]) {}",
|
||||
"function foo([, ,] = [, ,]) {\n}\n")
|
||||
}
|
||||
|
||||
func TestGenerator(t *testing.T) {
|
||||
expectPrinted(t,
|
||||
"function* foo() {}",
|
||||
"function* foo() {\n}\n")
|
||||
expectPrinted(t,
|
||||
"(function* () {})",
|
||||
"(function* () {\n});\n")
|
||||
expectPrinted(t,
|
||||
"(function* foo() {})",
|
||||
"(function* foo() {\n});\n")
|
||||
|
||||
expectPrinted(t,
|
||||
"class Foo { *foo() {} }",
|
||||
"class Foo {\n *foo() {\n }\n}\n")
|
||||
expectPrinted(t,
|
||||
"class Foo { static *foo() {} }",
|
||||
"class Foo {\n static *foo() {\n }\n}\n")
|
||||
expectPrinted(t,
|
||||
"class Foo { *[foo]() {} }",
|
||||
"class Foo {\n *[foo]() {\n }\n}\n")
|
||||
expectPrinted(t,
|
||||
"class Foo { static *[foo]() {} }",
|
||||
"class Foo {\n static *[foo]() {\n }\n}\n")
|
||||
|
||||
expectPrinted(t,
|
||||
"(class { *foo() {} })",
|
||||
"(class {\n *foo() {\n }\n});\n")
|
||||
expectPrinted(t,
|
||||
"(class { static *foo() {} })",
|
||||
"(class {\n static *foo() {\n }\n});\n")
|
||||
expectPrinted(t,
|
||||
"(class { *[foo]() {} })",
|
||||
"(class {\n *[foo]() {\n }\n});\n")
|
||||
expectPrinted(t,
|
||||
"(class { static *[foo]() {} })",
|
||||
"(class {\n static *[foo]() {\n }\n});\n")
|
||||
}
|
||||
|
||||
func TestArrow(t *testing.T) {
|
||||
expectPrinted(t, "() => {}", "() => {\n};\n")
|
||||
expectPrinted(t, "x => (x, 0)", "(x) => (x, 0);\n")
|
||||
expectPrinted(t, "x => {y}", "(x) => {\n y;\n};\n")
|
||||
expectPrinted(t,
|
||||
"(a = (b, c), ...d) => {}",
|
||||
"(a = (b, c), ...d) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"({[1 + 2]: a = 3} = {[1 + 2]: 3}) => {}",
|
||||
"({[1 + 2]: a = 3} = {\n [1 + 2]: 3\n}) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"([a = (1, 2), ...[b, ...c]] = [1, [2, 3]]) => {}",
|
||||
"([a = (1, 2), ...[b, ...c]] = [1, [2, 3]]) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"([] = []) => {}",
|
||||
"([] = []) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"([,] = [,]) => {}",
|
||||
"([,] = [,]) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"([,,] = [,,]) => {}",
|
||||
"([, ,] = [, ,]) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"a = () => {}",
|
||||
"a = () => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"a || (() => {})",
|
||||
"a || (() => {\n});\n")
|
||||
expectPrinted(t,
|
||||
"({a = b, c = d}) => {}",
|
||||
"({a = b, c = d}) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"([{a = b, c = d} = {}] = []) => {}",
|
||||
"([{a = b, c = d} = {}] = []) => {\n};\n")
|
||||
expectPrinted(t,
|
||||
"({a: [b = c] = []} = {}) => {}",
|
||||
"({a: [b = c] = []} = {}) => {\n};\n")
|
||||
|
||||
// These are not arrow functions but initially look like one
|
||||
expectPrinted(t, "(a = b, c)", "a = b, c;\n")
|
||||
expectPrinted(t, "([...a = b])", "[...a = b];\n")
|
||||
expectPrinted(t, "([...a, ...b])", "[...a, ...b];\n")
|
||||
expectPrinted(t, "({a: b, c() {}})", "({\n a: b,\n c() {\n }\n});\n")
|
||||
expectPrinted(t, "({a: b, get c() {}})", "({\n a: b,\n get c() {\n }\n});\n")
|
||||
expectPrinted(t, "({a: b, set c() {}})", "({\n a: b,\n set c() {\n }\n});\n")
|
||||
}
|
||||
|
||||
func TestClass(t *testing.T) {
|
||||
expectPrinted(t, "class Foo extends (a, b) {}", "class Foo extends (a, b) {\n}\n")
|
||||
expectPrinted(t, "class Foo { get foo() {} }", "class Foo {\n get foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { set foo() {} }", "class Foo {\n set foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static foo() {} }", "class Foo {\n static foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static get foo() {} }", "class Foo {\n static get foo() {\n }\n}\n")
|
||||
expectPrinted(t, "class Foo { static set foo() {} }", "class Foo {\n static set foo() {\n }\n}\n")
|
||||
}
|
||||
|
||||
func TestImport(t *testing.T) {
|
||||
expectPrinted(t, "import('path');", "import(\"path\");\n") // The semicolon must not be a separate statement
|
||||
}
|
||||
|
||||
func TestExportDefault(t *testing.T) {
|
||||
expectPrinted(t, "export default function() {}", "export default function() {\n}\n")
|
||||
expectPrinted(t, "export default function foo() {}", "export default function foo() {\n}\n")
|
||||
expectPrinted(t, "export default class {}", "export default class {\n}\n")
|
||||
expectPrinted(t, "export default class foo {}", "export default class foo {\n}\n")
|
||||
expectPrinted(t, "export default (function() {})", "export default (function() {\n});\n")
|
||||
expectPrinted(t, "export default (function foo() {})", "export default (function foo() {\n});\n")
|
||||
expectPrinted(t, "export default (class {})", "export default (class {\n});\n")
|
||||
expectPrinted(t, "export default (class foo {})", "export default (class foo {\n});\n")
|
||||
}
|
||||
|
||||
func TestWhitespace(t *testing.T) {
|
||||
expectPrinted(t, "- -x", "- -x;\n")
|
||||
expectPrinted(t, "+ -x", "+-x;\n")
|
||||
expectPrinted(t, "- +x", "-+x;\n")
|
||||
expectPrinted(t, "+ +x", "+ +x;\n")
|
||||
expectPrinted(t, "- --x", "- --x;\n")
|
||||
expectPrinted(t, "+ --x", "+--x;\n")
|
||||
expectPrinted(t, "- ++x", "-++x;\n")
|
||||
expectPrinted(t, "+ ++x", "+ ++x;\n")
|
||||
|
||||
expectPrintedMinify(t, "- -x", "- -x;\n")
|
||||
expectPrintedMinify(t, "+ -x", "+-x;\n")
|
||||
expectPrintedMinify(t, "- +x", "-+x;\n")
|
||||
expectPrintedMinify(t, "+ +x", "+ +x;\n")
|
||||
expectPrintedMinify(t, "- --x", "- --x;\n")
|
||||
expectPrintedMinify(t, "+ --x", "+--x;\n")
|
||||
expectPrintedMinify(t, "- ++x", "-++x;\n")
|
||||
expectPrintedMinify(t, "+ ++x", "+ ++x;\n")
|
||||
|
||||
expectPrintedMinify(t, "x - --y", "x- --y;\n")
|
||||
expectPrintedMinify(t, "x + --y", "x+--y;\n")
|
||||
expectPrintedMinify(t, "x - ++y", "x-++y;\n")
|
||||
expectPrintedMinify(t, "x + ++y", "x+ ++y;\n")
|
||||
|
||||
expectPrintedMinify(t, "x-- > y", "x-- >y;\n")
|
||||
expectPrintedMinify(t, "x < !--y", "x<! --y;\n")
|
||||
expectPrintedMinify(t, "x > !--y", "x>!--y;\n")
|
||||
expectPrintedMinify(t, "!--y", "!--y;\n")
|
||||
|
||||
expectPrintedMinify(t, "/x/ / /y/", "/x// /y/;\n")
|
||||
|
||||
expectPrintedMinify(t, "throw x", "throw x;\n")
|
||||
expectPrintedMinify(t, "throw typeof x", "throw typeof x;\n")
|
||||
expectPrintedMinify(t, "throw delete x", "throw delete x;\n")
|
||||
expectPrintedMinify(t, "throw function(){}", "throw function(){};\n")
|
||||
|
||||
expectPrintedMinify(t, "x in function(){}", "x in function(){};\n")
|
||||
expectPrintedMinify(t, "x instanceof function(){}", "x instanceof function(){};\n")
|
||||
|
||||
expectPrintedMinify(t, "()=>({})", "()=>({});\n")
|
||||
expectPrintedMinify(t, "()=>({}[1])", "()=>({})[1];\n")
|
||||
expectPrintedMinify(t, "()=>({}+0)", "()=>({})+0;\n")
|
||||
expectPrintedMinify(t, "()=>function(){}", "()=>function(){};\n")
|
||||
|
||||
expectPrintedMinify(t, "(function(){})", "(function(){});\n")
|
||||
expectPrintedMinify(t, "(class{})", "(class{});\n")
|
||||
expectPrintedMinify(t, "({})", "({});\n")
|
||||
|
||||
expectPrintedMinify(t, "let x = '\\n'", "let x=`\n`;\n")
|
||||
}
|
||||
|
||||
func TestMinify(t *testing.T) {
|
||||
expectPrintedMinify(t, "0.1", ".1;\n")
|
||||
expectPrintedMinify(t, "1.2", "1.2;\n")
|
||||
|
||||
expectPrintedMinify(t, "() => {}", "()=>{};\n")
|
||||
expectPrintedMinify(t, "(a) => {}", "a=>{};\n")
|
||||
expectPrintedMinify(t, "(a = 0) => {}", "(a=0)=>{};\n")
|
||||
expectPrintedMinify(t, "(a, b) => {}", "(a,b)=>{};\n")
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"esbuild/ast"
|
||||
"esbuild/lexer"
|
||||
"esbuild/logging"
|
||||
"esbuild/parser"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Resolver interface {
|
||||
Resolve(sourcePath string, importPath string) (string, bool)
|
||||
Read(path string) (string, bool)
|
||||
FileExists(path string) bool
|
||||
PrettyPath(path string) string
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This is a dummy Resolver just for tests
|
||||
|
||||
type mapResolver struct {
|
||||
files map[string]string
|
||||
}
|
||||
|
||||
func CreateMapResolver(files map[string]string) Resolver {
|
||||
return &mapResolver{files}
|
||||
}
|
||||
|
||||
func (r *mapResolver) Resolve(sourcePath string, importPath string) (string, bool) {
|
||||
return commonResolve(r, sourcePath, importPath)
|
||||
}
|
||||
|
||||
func (r *mapResolver) Read(path string) (string, bool) {
|
||||
contents, ok := r.files[path]
|
||||
return contents, ok
|
||||
}
|
||||
|
||||
func (r *mapResolver) FileExists(path string) bool {
|
||||
_, ok := r.files[path]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *mapResolver) PrettyPath(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This is the Resolver that uses the actual file system
|
||||
|
||||
type fileSystemResolver struct {
|
||||
// Stores the contents of files we've read before
|
||||
fileContentsMutex sync.Mutex
|
||||
fileContents map[string]*string
|
||||
|
||||
// Stores the file entries for directories we've listed before
|
||||
fileEntriesMutex sync.Mutex
|
||||
fileEntries map[string]map[string]bool
|
||||
}
|
||||
|
||||
func CreateFileSystemResolver() Resolver {
|
||||
return &fileSystemResolver{
|
||||
sync.Mutex{},
|
||||
make(map[string]*string),
|
||||
sync.Mutex{},
|
||||
make(map[string]map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *fileSystemResolver) Resolve(sourcePath string, importPath string) (string, bool) {
|
||||
return commonResolve(r, sourcePath, importPath)
|
||||
}
|
||||
|
||||
func (r *fileSystemResolver) Read(path string) (string, bool) {
|
||||
// First, check the cache
|
||||
cached, ok := func() (*string, bool) {
|
||||
r.fileContentsMutex.Lock()
|
||||
defer r.fileContentsMutex.Unlock()
|
||||
cached, ok := r.fileContents[path]
|
||||
return cached, ok
|
||||
}()
|
||||
|
||||
// Cache hit: stop now
|
||||
if ok {
|
||||
if cached == nil {
|
||||
return "", false
|
||||
}
|
||||
return *cached, true
|
||||
}
|
||||
|
||||
// Cache miss: read the file
|
||||
buffer, err := ioutil.ReadFile(path)
|
||||
|
||||
// Update the cache unconditionally. Even if the read failed, we don't want to
|
||||
// retry again later. The file is inaccessible so trying again is wasted.
|
||||
r.fileContentsMutex.Lock()
|
||||
defer r.fileContentsMutex.Unlock()
|
||||
if err != nil {
|
||||
r.fileContents[path] = nil
|
||||
return "", false
|
||||
}
|
||||
contents := string(buffer)
|
||||
r.fileContents[path] = &contents
|
||||
return contents, true
|
||||
}
|
||||
|
||||
func readdir(dirname string) ([]string, error) {
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return f.Readdirnames(-1)
|
||||
}
|
||||
|
||||
func (r *fileSystemResolver) FileExists(path string) bool {
|
||||
dir := filepath.Dir(path)
|
||||
base := filepath.Base(path)
|
||||
|
||||
// Assume the root of the file system exists
|
||||
if path == dir {
|
||||
return true
|
||||
}
|
||||
|
||||
// First, check the cache
|
||||
cached, ok := func() (map[string]bool, bool) {
|
||||
r.fileEntriesMutex.Lock()
|
||||
defer r.fileEntriesMutex.Unlock()
|
||||
cached, ok := r.fileEntries[dir]
|
||||
return cached, ok
|
||||
}()
|
||||
|
||||
// Cache hit: stop now
|
||||
if ok {
|
||||
if cached == nil {
|
||||
return false
|
||||
}
|
||||
return cached[base]
|
||||
}
|
||||
|
||||
// Cache miss: read the directory entries
|
||||
names, err := readdir(dir)
|
||||
fileEntries := make(map[string]bool)
|
||||
if err == nil {
|
||||
for _, name := range names {
|
||||
// Use "stat", not "lstat", because we want to follow symbolic links
|
||||
if stat, err := os.Stat(filepath.Join(dir, name)); err == nil && !stat.IsDir() {
|
||||
fileEntries[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the cache unconditionally. Even if the read failed, we don't want to
|
||||
// retry again later. The directory is inaccessible so trying again is wasted.
|
||||
r.fileEntriesMutex.Lock()
|
||||
defer r.fileEntriesMutex.Unlock()
|
||||
if err != nil {
|
||||
r.fileEntries[dir] = nil
|
||||
return false
|
||||
}
|
||||
r.fileEntries[dir] = fileEntries
|
||||
return fileEntries[base]
|
||||
}
|
||||
|
||||
func (*fileSystemResolver) PrettyPath(path string) string {
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
if rel, err := filepath.Rel(cwd, path); err == nil {
|
||||
return rel
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var extensionOrder = []string{".jsx", ".js", ".json"}
|
||||
|
||||
func loadAsFile(r Resolver, path string) (string, bool) {
|
||||
if r.FileExists(path) {
|
||||
return path, true
|
||||
}
|
||||
|
||||
for _, ext := range extensionOrder {
|
||||
extPath := path + ext
|
||||
if r.FileExists(extPath) {
|
||||
return extPath, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func loadAsIndex(r Resolver, path string) (string, bool) {
|
||||
jsPath := filepath.Join(path, "index.js")
|
||||
if r.FileExists(jsPath) {
|
||||
return jsPath, true
|
||||
}
|
||||
jsonPath := filepath.Join(path, "index.json")
|
||||
if r.FileExists(jsonPath) {
|
||||
return jsonPath, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func parseMainFromJson(r Resolver, path string) (result string, found bool) {
|
||||
// Read the file
|
||||
contents, ok := r.Read(path)
|
||||
if ok {
|
||||
// Parse the JSON
|
||||
log, _ := logging.NewDeferLog()
|
||||
source := logging.Source{Contents: contents}
|
||||
parsed, ok := parser.ParseJson(log, source)
|
||||
if ok {
|
||||
// Check for a top-level object
|
||||
if obj, ok := parsed.Data.(*ast.EObject); ok {
|
||||
for _, prop := range obj.Properties {
|
||||
// Find the key that says "main"
|
||||
if key, ok := prop.Key.Data.(*ast.EString); ok && len(key.Value) == 4 && lexer.UTF16ToString(key.Value) == "main" {
|
||||
if value, ok := prop.Value.Data.(*ast.EString); ok {
|
||||
// Return the value for this key if it's a string
|
||||
result = lexer.UTF16ToString(value.Value)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func loadAsFileOrDirectory(r Resolver, path string) (string, bool) {
|
||||
absolute, ok := loadAsFile(r, path)
|
||||
if ok {
|
||||
return absolute, true
|
||||
}
|
||||
|
||||
packageJson := filepath.Join(path, "package.json")
|
||||
if r.FileExists(packageJson) {
|
||||
if main, ok := parseMainFromJson(r, packageJson); ok {
|
||||
mainPath := filepath.Join(path, main)
|
||||
|
||||
absolute, ok := loadAsFile(r, mainPath)
|
||||
if ok {
|
||||
return absolute, true
|
||||
}
|
||||
|
||||
absolute, ok = loadAsIndex(r, mainPath)
|
||||
if ok {
|
||||
return absolute, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loadAsIndex(r, path)
|
||||
}
|
||||
|
||||
func loadNodeModules(r Resolver, path string, start string) (string, bool) {
|
||||
for {
|
||||
// Skip "node_modules" folders
|
||||
if filepath.Base(start) != "node_modules" {
|
||||
absolute, ok := loadAsFileOrDirectory(r, filepath.Join(start, "node_modules", path))
|
||||
if ok {
|
||||
return absolute, true
|
||||
}
|
||||
}
|
||||
|
||||
// Go to the parent directory, stopping at the file system root
|
||||
dir := filepath.Dir(start)
|
||||
if start == dir {
|
||||
break
|
||||
}
|
||||
start = dir
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func loadSelfReference(r Resolver, path string, start string) (string, bool) {
|
||||
// Note: this is modified from how node's resolution algorithm works. Instead
|
||||
// of just checking the closest enclosing directory with a "package.json"
|
||||
// file, it checks all enclosing directories.
|
||||
|
||||
for {
|
||||
// Check this directory
|
||||
absolute, ok := loadAsFileOrDirectory(r, filepath.Join(start, path))
|
||||
if ok {
|
||||
return absolute, true
|
||||
}
|
||||
|
||||
// Go to the parent directory, stopping at the file system root
|
||||
dir := filepath.Dir(start)
|
||||
if start == dir {
|
||||
break
|
||||
}
|
||||
start = dir
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func commonResolve(r Resolver, sourcePath string, importPath string) (string, bool) {
|
||||
// This implements the module resolution algorithm from node.js, which is
|
||||
// described here: https://nodejs.org/api/modules.html#modules_all_together
|
||||
|
||||
sourceDir := filepath.Dir(sourcePath)
|
||||
startsWithSlash := strings.HasPrefix(importPath, "/")
|
||||
|
||||
if startsWithSlash {
|
||||
sourceDir = "/"
|
||||
}
|
||||
|
||||
if startsWithSlash || strings.HasPrefix(importPath, "./") || strings.HasPrefix(importPath, "../") {
|
||||
return loadAsFileOrDirectory(r, filepath.Join(sourceDir, importPath))
|
||||
}
|
||||
|
||||
absolute, ok := loadNodeModules(r, importPath, sourceDir)
|
||||
if ok {
|
||||
return absolute, true
|
||||
}
|
||||
|
||||
return loadSelfReference(r, importPath, sourceDir)
|
||||
}
|
||||
Reference in New Issue
Block a user