Skip to content

Commit

Permalink
parse let as symbol names correctly (#5151)
Browse files Browse the repository at this point in the history
fixes #5149
  • Loading branch information
alexlamsl committed Oct 20, 2021
1 parent 32ae994 commit 2580162
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 19 deletions.
41 changes: 25 additions & 16 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@

"use strict";

var KEYWORDS = "break case catch class const continue debugger default delete do else extends finally for function if in instanceof let new return switch throw try typeof var void while with";
var KEYWORDS = "break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var void while with";
var KEYWORDS_ATOM = "false null true";
var RESERVED_WORDS = [
"abstract async await boolean byte char double enum export final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield",
"abstract async await boolean byte char double enum export final float goto implements import int interface let long native package private protected public short static super synchronized this throws transient volatile yield",
KEYWORDS_ATOM,
KEYWORDS,
].join(" ");
Expand Down Expand Up @@ -867,6 +867,15 @@ function parse($TEXT, options) {
next();
return import_();
}
break;
case "let":
if (is_vardefs()) {
next();
var node = let_();
semicolon();
return node;
}
break;
case "yield":
if (S.in_generator) return simple_statement();
break;
Expand Down Expand Up @@ -952,12 +961,6 @@ function parse($TEXT, options) {
next();
return if_();

case "let":
next();
var node = let_();
semicolon();
return node;

case "return":
if (S.in_function == 0 && !options.bare_returns)
croak("'return' outside of function");
Expand Down Expand Up @@ -1197,7 +1200,7 @@ function parse($TEXT, options) {
if (await || !is("punc", ";")) {
init = is("keyword", "const")
? (next(), const_(true))
: is("keyword", "let")
: is("name", "let") && is_vardefs()
? (next(), let_(true))
: is("keyword", "var")
? (next(), var_(true))
Expand Down Expand Up @@ -1540,12 +1543,18 @@ function parse($TEXT, options) {
}

var export_decl = embed_tokens(function() {
if (is("name", "async")) {
if (is("name")) switch (S.token.value) {
case "async":
next();
expect_token("keyword", "function");
if (!is("operator", "*")) return function_(AST_AsyncDefun);
next();
return function_(AST_AsyncGeneratorDefun);
case "let":
next();
var node = let_();
semicolon();
return node;
} else if (is("keyword")) switch (S.token.value) {
case "class":
next();
Expand All @@ -1560,11 +1569,6 @@ function parse($TEXT, options) {
if (!is("operator", "*")) return function_(AST_Defun);
next();
return function_(AST_GeneratorDefun);
case "let":
next();
var node = let_();
semicolon();
return node;
case "var":
next();
var node = var_();
Expand Down Expand Up @@ -1725,6 +1729,11 @@ function parse($TEXT, options) {
return a;
}

function is_vardefs() {
var token = peek();
return is_token(token, "name") || is_token(token, "punc", "[") || is_token(token, "punc", "{");
}

var const_ = function(no_in) {
return new AST_Const({
start : prev(),
Expand Down Expand Up @@ -2135,7 +2144,7 @@ function parse($TEXT, options) {
}

function strict_verify_symbol(sym) {
if (sym.name == "arguments" || sym.name == "eval")
if (sym.name == "arguments" || sym.name == "eval" || sym.name == "let")
token_error(sym.start, "Unexpected " + sym.name + " in strict mode");
}

Expand Down
41 changes: 40 additions & 1 deletion test/mocha/let.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var assert = require("assert");
var UglifyJS = require("../..");
var UglifyJS = require("../node");

describe("let", function() {
this.timeout(30000);
Expand Down Expand Up @@ -54,4 +54,43 @@ describe("let", function() {
assert.notStrictEqual(result.indexOf('v["' + name + '"]'), -1);
});
});
it("Should parse `let` as name correctly", function() {
[
"for(var let;let;let)let;",
"function let(let){let}",
].forEach(function(code) {
var ast = UglifyJS.parse(code);
assert.strictEqual(ast.print_to_string(), code);
assert.throws(function() {
UglifyJS.parse('"use strict";' + code);
}, function(e) {
return e instanceof UglifyJS.JS_Parse_Error && e.message == "Unexpected let in strict mode";
}, code);
});
});
it("Should throw on ambiguous use of `let`", function() {
[
"export let",
[
"let",
"console.log(42)",
].join("\n"),
[
"let",
"[ console.log(42) ]",
].join("\n"),
[
"let",
"{",
" console.log(42)",
"}",
].join("\n"),
].forEach(function(code) {
assert.throws(function() {
UglifyJS.parse(code);
}, function(e) {
return e instanceof UglifyJS.JS_Parse_Error;
}, code);
});
});
});
6 changes: 4 additions & 2 deletions test/ufuzz/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ var VAR_NAMES = [
"arguments",
"async",
"await",
"let",
"yield",
];
var INITIAL_NAMES_LEN = VAR_NAMES.length;
Expand All @@ -352,7 +353,7 @@ var TYPEOF_OUTCOMES = [
];

var avoid_vars = [];
var block_vars = [];
var block_vars = [ "let" ];
var lambda_vars = [];
var unique_vars = [];
var classes = [];
Expand Down Expand Up @@ -399,7 +400,7 @@ function mayDefer(code) {

function createTopLevelCode() {
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
block_vars.length = 0;
block_vars.length = 1;
lambda_vars.length = 0;
unique_vars.length = 0;
classes.length = 0;
Expand Down Expand Up @@ -2027,6 +2028,7 @@ function removeAvoidVar(name) {
function isBannedKeyword(name) {
switch (name) {
case "arguments":
case "let":
return in_class;
case "await":
return async !== false;
Expand Down

0 comments on commit 2580162

Please sign in to comment.