put on github

This commit is contained in:
Evan Wallace
2020-01-15 03:20:24 -08:00
commit 23c40b1b6a
28 changed files with 25361 additions and 0 deletions
+10
View File
@@ -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
View File
@@ -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.
+163
View File
@@ -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*
+97
View File
@@ -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`.
![](images/benchmark.png)
| 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

+16
View File
@@ -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"
}
}
+16
View File
@@ -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"
}
}
+9
View File
@@ -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 }));
+13
View File
@@ -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"
}
}
+14
View File
@@ -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' });
+16
View File
@@ -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"
}
}
+8528
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -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"
}
}
+152
View File
@@ -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
}));
+947
View File
@@ -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
+291
View File
@@ -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
+521
View File
@@ -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
+453
View File
@@ -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}
}
+356
View File
@@ -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
+129
View File
@@ -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{})
}
+944
View File
@@ -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=\"&lt;&gt;\"/>", "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>&lt;&gt;</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
+321
View File
@@ -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")
}
+326
View File
@@ -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)
}