From d576495e5a696302340580e999bc0446a1d4c1ca Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 28 Apr 2021 21:13:42 +0100 Subject: [PATCH] support `#__PURE__` in ESTree (#4879) --- .github/workflows/build.yml | 1 + bin/uglifyjs | 29 +++++++++++++++++++++++++- lib/mozilla-ast.js | 30 +++++++++++++++++++++++---- lib/output.js | 2 +- test/release/rollup-ts.sh | 16 ++++++++------ test/release/sucrase.sh | 16 ++++++++------ test/release/web-tooling-benchmark.sh | 13 +++++++++++- 7 files changed, 88 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 381b79aeaa..9dc292c354 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ jobs: - '-mb braces' - '--ie8 -c' - '-mc' + - '-p acorn --toplevel -mco spidermonkey' - '--toplevel -mc passes=3,pure_getters,unsafe' script: - acorn.sh diff --git a/bin/uglifyjs b/bin/uglifyjs index 69a98a4bd8..2d03d99ac6 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -314,16 +314,43 @@ function run() { try { if (options.parse) { if (options.parse.acorn) { + var annotations = Object.create(null); files = convert_ast(function(toplevel, name) { - return require("acorn").parse(files[name], { + var content = files[name]; + var list = annotations[name] = []; + var prev = -1; + return require("acorn").parse(content, { allowHashBang: true, ecmaVersion: "latest", locations: true, + onComment: function(block, text, start, end) { + var match = /[@#]__PURE__/.exec(text); + if (!match) { + if (start != prev) return; + match = [ list[prev] ]; + } + while (/\s/.test(content[end])) end++; + list[end] = match[0]; + prev = end; + }, + preserveParens: true, program: toplevel, sourceFile: name, sourceType: "module", }); }); + files.walk(new UglifyJS.TreeWalker(function(node) { + if (!(node instanceof UglifyJS.AST_Call)) return; + var list = annotations[node.start.file]; + var pure = list[node.start.pos]; + if (!pure) { + var pos = node.start.parens; + if (pos) for (var i = 0; !pure && i < pos.length; i++) { + pure = list[pos[i]]; + } + } + if (pure) node.pure = pure; + })); } else if (options.parse.spidermonkey) { files = convert_ast(function(toplevel, name) { var obj = JSON.parse(files[name]); diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 028441b760..bfc6ac3126 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -446,8 +446,22 @@ args.value = val; return new AST_String(args); case "number": - args.value = val; - return new AST_Number(args); + if (isNaN(val)) return new AST_NaN(args); + var negate, node; + if (isFinite(val)) { + negate = 1 / val < 0; + args.value = negate ? -val : val; + node = new AST_Number(args); + } else { + negate = val < 0; + node = new AST_Infinity(args); + } + return negate ? new AST_UnaryPrefix({ + start: args.start, + end: args.end, + operator: "-", + expression: node, + }) : node; case "boolean": return new (val ? AST_True : AST_False)(args); } @@ -532,6 +546,14 @@ name: "this", }); }, + ParenthesizedExpression: function(M) { + var node = from_moz(M.expression); + if (!node.start.parens) node.start.parens = []; + node.start.parens.push(my_start_token(M)); + if (!node.end.parens) node.end.parens = []; + node.end.parens.push(my_end_token(M)); + return node; + }, }; MOZ_TO_ME.UpdateExpression = @@ -570,8 +592,8 @@ map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right"); map("AssignmentPattern", AST_DefaultValue, "left>name, right>value"); map("ConditionalExpression", AST_Conditional, "test>condition, consequent>consequent, alternate>alternative"); - map("NewExpression", AST_New, "callee>expression, arguments@args"); - map("CallExpression", AST_Call, "callee>expression, arguments@args"); + map("NewExpression", AST_New, "callee>expression, arguments@args, pure=pure"); + map("CallExpression", AST_Call, "callee>expression, arguments@args, pure=pure"); map("SequenceExpression", AST_Sequence, "expressions@expressions"); map("SpreadElement", AST_Spread, "argument>expression"); map("ObjectExpression", AST_Object, "properties@properties"); diff --git a/lib/output.js b/lib/output.js index 4351260665..75ccca2d98 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1456,7 +1456,7 @@ function OutputStream(options) { parent = output.parent(level++); if (parent instanceof AST_Call && parent.expression === node) return; } while (parent instanceof AST_PropAccess && parent.expression === node); - output.print("/*" + self.pure + "*/"); + output.print(typeof self.pure == "string" ? "/*" + self.pure + "*/" : "/*@__PURE__*/"); } function print_call_args(self, output) { if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) { diff --git a/test/release/rollup-ts.sh b/test/release/rollup-ts.sh index 0a960d552a..fa7c56f547 100755 --- a/test/release/rollup-ts.sh +++ b/test/release/rollup-ts.sh @@ -6,18 +6,22 @@ UGLIFY_OPTIONS=$@ minify_in_situ() { ARGS="$UGLIFY_OPTIONS --validate --in-situ" DIRS="$1" + echo '> esbuild' $DIRS + for i in `find $DIRS -type f -name '*.ts' | grep -v '\.d\.ts'` + do + echo "$i" + CODE=`cat "$i"` + node_modules/.bin/esbuild --loader=ts --target=es2019 > "$i" < uglify-js' $DIRS $UGLIFY_OPTIONS for i in `find $DIRS -type f -name '*.js'` do ARGS="$ARGS $i" done uglify-js $ARGS - for i in `find $DIRS -type f -name '*.ts' | grep -v '\.d\.ts'` - do - echo "$i" - node_modules/.bin/esbuild --loader=ts --target=es2019 < "$i" \ - | uglify-js $UGLIFY_OPTIONS -o "$i" - done } rm -rf tmp/rollup \ diff --git a/test/release/sucrase.sh b/test/release/sucrase.sh index 909e633eda..5e2c41bbf2 100755 --- a/test/release/sucrase.sh +++ b/test/release/sucrase.sh @@ -6,6 +6,16 @@ UGLIFY_OPTIONS=$@ minify_in_situ() { ARGS="$UGLIFY_OPTIONS --validate --in-situ" DIRS="$1" + echo '> esbuild' $DIRS + for i in `find $DIRS -type f -name '*.ts' | grep -v '\.d\.ts'` + do + echo "$i" + CODE=`cat "$i"` + node_modules/.bin/esbuild --loader=ts --target=es2019 > "$i" < uglify-js' $DIRS $UGLIFY_OPTIONS for i in `find $DIRS -type f -name '*.js'` do @@ -16,12 +26,6 @@ minify_in_situ() { ARGS="$ARGS $i" done uglify-js $ARGS - for i in `find $DIRS -type f -name '*.ts' | grep -v '\.d\.ts'` - do - echo "$i" - node_modules/.bin/esbuild --loader=ts --target=es2019 < "$i" \ - | uglify-js $UGLIFY_OPTIONS -o "$i" - done } rm -rf tmp/sucrase \ diff --git a/test/release/web-tooling-benchmark.sh b/test/release/web-tooling-benchmark.sh index d7218d38f8..425661370c 100755 --- a/test/release/web-tooling-benchmark.sh +++ b/test/release/web-tooling-benchmark.sh @@ -1,6 +1,6 @@ #!/bin/sh -alias uglify-js="node --max-old-space-size=4096 $PWD/bin/uglifyjs" +alias uglify-js="node --max-old-space-size=8192 $PWD/bin/uglifyjs" UGLIFY_OPTIONS=$@ minify_in_situ() { @@ -23,6 +23,17 @@ rm -rf tmp/web-tooling-benchmark \ +++ b/package.json @@ -12 +11,0 @@ - "postinstall": "npm run build:terser-bundled && npm run build:uglify-js-bundled && npm run build", +--- a/src/bootstrap.js ++++ b/src/bootstrap.js +@@ -6 +6 @@ const gmean = require("compute-gmean"); +-const package = require("../package.json"); ++const package_json = require("../package.json"); +@@ -65 +65 @@ function initialize() { +- document.title = \`Web Tooling Benchmark v\${package.version}\`; ++ document.title = \`Web Tooling Benchmark v\${package_json.version}\`; +@@ -68 +68 @@ function initialize() { +- versionDiv.innerHTML = \`v\${package.version}\`; ++ versionDiv.innerHTML = \`v\${package_json.version}\`; --- a/src/cli-flags-helper.js +++ b/src/cli-flags-helper.js @@ -7 +6,0 @@ const targetList = new Set([