Skip to content

Commit 2580162

Browse files
authored
parse let as symbol names correctly (#5151)
fixes #5149
1 parent 32ae994 commit 2580162

3 files changed

Lines changed: 69 additions & 19 deletions

File tree

lib/parse.js

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@
4444

4545
"use strict";
4646

47-
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";
47+
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";
4848
var KEYWORDS_ATOM = "false null true";
4949
var RESERVED_WORDS = [
50-
"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",
50+
"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",
5151
KEYWORDS_ATOM,
5252
KEYWORDS,
5353
].join(" ");
@@ -867,6 +867,15 @@ function parse($TEXT, options) {
867867
next();
868868
return import_();
869869
}
870+
break;
871+
case "let":
872+
if (is_vardefs()) {
873+
next();
874+
var node = let_();
875+
semicolon();
876+
return node;
877+
}
878+
break;
870879
case "yield":
871880
if (S.in_generator) return simple_statement();
872881
break;
@@ -952,12 +961,6 @@ function parse($TEXT, options) {
952961
next();
953962
return if_();
954963

955-
case "let":
956-
next();
957-
var node = let_();
958-
semicolon();
959-
return node;
960-
961964
case "return":
962965
if (S.in_function == 0 && !options.bare_returns)
963966
croak("'return' outside of function");
@@ -1197,7 +1200,7 @@ function parse($TEXT, options) {
11971200
if (await || !is("punc", ";")) {
11981201
init = is("keyword", "const")
11991202
? (next(), const_(true))
1200-
: is("keyword", "let")
1203+
: is("name", "let") && is_vardefs()
12011204
? (next(), let_(true))
12021205
: is("keyword", "var")
12031206
? (next(), var_(true))
@@ -1540,12 +1543,18 @@ function parse($TEXT, options) {
15401543
}
15411544

15421545
var export_decl = embed_tokens(function() {
1543-
if (is("name", "async")) {
1546+
if (is("name")) switch (S.token.value) {
1547+
case "async":
15441548
next();
15451549
expect_token("keyword", "function");
15461550
if (!is("operator", "*")) return function_(AST_AsyncDefun);
15471551
next();
15481552
return function_(AST_AsyncGeneratorDefun);
1553+
case "let":
1554+
next();
1555+
var node = let_();
1556+
semicolon();
1557+
return node;
15491558
} else if (is("keyword")) switch (S.token.value) {
15501559
case "class":
15511560
next();
@@ -1560,11 +1569,6 @@ function parse($TEXT, options) {
15601569
if (!is("operator", "*")) return function_(AST_Defun);
15611570
next();
15621571
return function_(AST_GeneratorDefun);
1563-
case "let":
1564-
next();
1565-
var node = let_();
1566-
semicolon();
1567-
return node;
15681572
case "var":
15691573
next();
15701574
var node = var_();
@@ -1725,6 +1729,11 @@ function parse($TEXT, options) {
17251729
return a;
17261730
}
17271731

1732+
function is_vardefs() {
1733+
var token = peek();
1734+
return is_token(token, "name") || is_token(token, "punc", "[") || is_token(token, "punc", "{");
1735+
}
1736+
17281737
var const_ = function(no_in) {
17291738
return new AST_Const({
17301739
start : prev(),
@@ -2135,7 +2144,7 @@ function parse($TEXT, options) {
21352144
}
21362145

21372146
function strict_verify_symbol(sym) {
2138-
if (sym.name == "arguments" || sym.name == "eval")
2147+
if (sym.name == "arguments" || sym.name == "eval" || sym.name == "let")
21392148
token_error(sym.start, "Unexpected " + sym.name + " in strict mode");
21402149
}
21412150

test/mocha/let.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var assert = require("assert");
2-
var UglifyJS = require("../..");
2+
var UglifyJS = require("../node");
33

44
describe("let", function() {
55
this.timeout(30000);
@@ -54,4 +54,43 @@ describe("let", function() {
5454
assert.notStrictEqual(result.indexOf('v["' + name + '"]'), -1);
5555
});
5656
});
57+
it("Should parse `let` as name correctly", function() {
58+
[
59+
"for(var let;let;let)let;",
60+
"function let(let){let}",
61+
].forEach(function(code) {
62+
var ast = UglifyJS.parse(code);
63+
assert.strictEqual(ast.print_to_string(), code);
64+
assert.throws(function() {
65+
UglifyJS.parse('"use strict";' + code);
66+
}, function(e) {
67+
return e instanceof UglifyJS.JS_Parse_Error && e.message == "Unexpected let in strict mode";
68+
}, code);
69+
});
70+
});
71+
it("Should throw on ambiguous use of `let`", function() {
72+
[
73+
"export let",
74+
[
75+
"let",
76+
"console.log(42)",
77+
].join("\n"),
78+
[
79+
"let",
80+
"[ console.log(42) ]",
81+
].join("\n"),
82+
[
83+
"let",
84+
"{",
85+
" console.log(42)",
86+
"}",
87+
].join("\n"),
88+
].forEach(function(code) {
89+
assert.throws(function() {
90+
UglifyJS.parse(code);
91+
}, function(e) {
92+
return e instanceof UglifyJS.JS_Parse_Error;
93+
}, code);
94+
});
95+
});
5796
});

test/ufuzz/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ var VAR_NAMES = [
334334
"arguments",
335335
"async",
336336
"await",
337+
"let",
337338
"yield",
338339
];
339340
var INITIAL_NAMES_LEN = VAR_NAMES.length;
@@ -352,7 +353,7 @@ var TYPEOF_OUTCOMES = [
352353
];
353354

354355
var avoid_vars = [];
355-
var block_vars = [];
356+
var block_vars = [ "let" ];
356357
var lambda_vars = [];
357358
var unique_vars = [];
358359
var classes = [];
@@ -399,7 +400,7 @@ function mayDefer(code) {
399400

400401
function createTopLevelCode() {
401402
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
402-
block_vars.length = 0;
403+
block_vars.length = 1;
403404
lambda_vars.length = 0;
404405
unique_vars.length = 0;
405406
classes.length = 0;
@@ -2027,6 +2028,7 @@ function removeAvoidVar(name) {
20272028
function isBannedKeyword(name) {
20282029
switch (name) {
20292030
case "arguments":
2031+
case "let":
20302032
return in_class;
20312033
case "await":
20322034
return async !== false;

0 commit comments

Comments
 (0)