Skip to content

Commit e37a9c3

Browse files
committed
Improve lookahead test for using syntax
FIX: Fix some corner case issues in the recognition of `using` syntax. Issue #1373
1 parent 55006bb commit e37a9c3

File tree

3 files changed

+260
-23
lines changed

3 files changed

+260
-23
lines changed

acorn/src/statement.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,21 @@ pp.isLet = function(context) {
3737
if (this.options.ecmaVersion < 6 || !this.isContextual("let")) return false
3838
skipWhiteSpace.lastIndex = this.pos
3939
let skip = skipWhiteSpace.exec(this.input)
40-
let next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next)
40+
let next = this.pos + skip[0].length, nextCh = this.fullCharCodeAt(next)
4141
// For ambiguous cases, determine if a LexicalDeclaration (or only a
4242
// Statement) is allowed here. If context is not empty then only a Statement
4343
// is allowed. However, `let [` is an explicit negative lookahead for
4444
// ExpressionStatement, so special-case it first.
4545
if (nextCh === 91 || nextCh === 92) return true // '[', '\'
4646
if (context) return false
4747

48-
if (nextCh === 123 || nextCh > 0xd7ff && nextCh < 0xdc00) return true // '{', astral
48+
if (nextCh === 123) return true // '{'
4949
if (isIdentifierStart(nextCh, true)) {
50-
let pos = next + 1
51-
while (isIdentifierChar(nextCh = this.input.charCodeAt(pos), true)) ++pos
52-
if (nextCh === 92 || nextCh > 0xd7ff && nextCh < 0xdc00) return true
53-
let ident = this.input.slice(next, pos)
50+
let start = next
51+
do { next += nextCh <= 0xffff ? 1 : 2 }
52+
while (isIdentifierChar(nextCh = this.fullCharCodeAt(next), true))
53+
if (nextCh === 92) return true
54+
let ident = this.input.slice(start, next)
5455
if (!keywordRelationalOperator.test(ident)) return true
5556
}
5657
return false
@@ -92,19 +93,19 @@ pp.isUsingKeyword = function(isAwaitUsing, isFor) {
9293

9394
skipWhiteSpace.lastIndex = awaitEndPos
9495
let skipAfterUsing = skipWhiteSpace.exec(this.input)
95-
if (skipAfterUsing && lineBreak.test(this.input.slice(awaitEndPos, awaitEndPos + skipAfterUsing[0].length))) return false
96+
next = awaitEndPos + skipAfterUsing[0].length
97+
if (skipAfterUsing && lineBreak.test(this.input.slice(awaitEndPos, next))) return false
9698
}
9799

98-
if (isFor) {
99-
let ofEndPos = next + 2 /* of */, after
100-
if (this.input.slice(next, ofEndPos) === "of") {
101-
if (ofEndPos === this.input.length ||
102-
(!isIdentifierChar(after = this.input.charCodeAt(ofEndPos)) && !(after > 0xd7ff && after < 0xdc00))) return false
103-
}
104-
}
105-
106-
let ch = this.input.charCodeAt(next)
107-
return isIdentifierStart(ch, true) || ch === 92 // '\'
100+
let ch = this.fullCharCodeAt(next)
101+
if (!isIdentifierStart(ch, true) && ch !== 92 /* '\' */) return false
102+
let idStart = next
103+
do { next += ch <= 0xffff ? 1 : 2 }
104+
while (isIdentifierChar(ch = this.fullCharCodeAt(next)))
105+
if (ch === 92) return true
106+
let id = this.input.slice(idStart, next)
107+
if (keywordRelationalOperator.test(id) || isFor && id === "of") return false
108+
return true
108109
}
109110

110111
pp.isAwaitUsing = function(isFor) {

acorn/src/tokenize.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,17 @@ pp.readToken = function(code) {
8888
return this.getTokenFromCode(code)
8989
}
9090

91-
pp.fullCharCodeAtPos = function() {
92-
let code = this.input.charCodeAt(this.pos)
91+
pp.fullCharCodeAt = function(pos) {
92+
let code = this.input.charCodeAt(pos)
9393
if (code <= 0xd7ff || code >= 0xdc00) return code
94-
let next = this.input.charCodeAt(this.pos + 1)
94+
let next = this.input.charCodeAt(pos + 1)
9595
return next <= 0xdbff || next >= 0xe000 ? code : (code << 10) + next - 0x35fdc00
9696
}
9797

98+
pp.fullCharCodeAtPos = function() {
99+
return this.fullCharCodeAt(this.pos)
100+
}
101+
98102
pp.skipBlockComment = function() {
99103
let startLoc = this.options.onComment && this.curPosition()
100104
let start = this.pos, end = this.input.indexOf("*/", this.pos += 2)

test/tests-using.js

Lines changed: 235 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ testFail("function* gen() { using yield = resource; }", "Cannot use 'yield' as i
11541154
testFail("async function test() { using await = resource; }", "Cannot use 'await' as identifier inside an async function (1:30)", {ecmaVersion: 17, sourceType: "module"});
11551155

11561156
// Rest elements are not allowed in await using declarations
1157-
testFail("async function test() { await using [first, ...rest] = arr; }", "Unexpected token (1:36)", {ecmaVersion: 17, sourceType: "module"});
1157+
testFail("async function test() { await using [first, ...rest] = arr; }", "Unexpected token (1:44)", {ecmaVersion: 17, sourceType: "module"});
11581158

11591159
// Strict mode restrictions with await using
11601160
testFail("'use strict'; async function test() { await using arguments = resource; }", "Binding arguments in strict mode (1:50)", {ecmaVersion: 17, sourceType: "module"});
@@ -1493,7 +1493,7 @@ test("for (using of x) {}", {
14931493
}, {ecmaVersion: 16, sourceType: "script"});
14941494

14951495
// ES17: using should be treated as regular identifier (for-of)
1496-
test("for (using of x) {}", {
1496+
test("for (using of y) {}", {
14971497
type: "Program",
14981498
start: 0,
14991499
end: 19,
@@ -1512,7 +1512,7 @@ test("for (using of x) {}", {
15121512
type: "Identifier",
15131513
start: 14,
15141514
end: 15,
1515-
name: "x"
1515+
name: "y"
15161516
},
15171517
body: {
15181518
type: "BlockStatement",
@@ -2750,3 +2750,235 @@ test("async function test() { await usingX; }", {
27502750
}],
27512751
sourceType: "module"
27522752
}, {ecmaVersion: 17, sourceType: "module"});
2753+
2754+
test(`async function f() {
2755+
await using in foo;
2756+
}`, {
2757+
"type": "Program",
2758+
"start": 0,
2759+
"end": 44,
2760+
"body": [
2761+
{
2762+
"type": "FunctionDeclaration",
2763+
"start": 0,
2764+
"end": 44,
2765+
"id": {
2766+
"type": "Identifier",
2767+
"start": 15,
2768+
"end": 16,
2769+
"name": "f"
2770+
},
2771+
"expression": false,
2772+
"generator": false,
2773+
"async": true,
2774+
"params": [],
2775+
"body": {
2776+
"type": "BlockStatement",
2777+
"start": 19,
2778+
"end": 44,
2779+
"body": [
2780+
{
2781+
"type": "ExpressionStatement",
2782+
"start": 23,
2783+
"end": 42,
2784+
"expression": {
2785+
"type": "BinaryExpression",
2786+
"start": 23,
2787+
"end": 41,
2788+
"left": {
2789+
"type": "AwaitExpression",
2790+
"start": 23,
2791+
"end": 34,
2792+
"argument": {
2793+
"type": "Identifier",
2794+
"start": 29,
2795+
"end": 34,
2796+
"name": "using"
2797+
}
2798+
},
2799+
"operator": "in",
2800+
"right": {
2801+
"type": "Identifier",
2802+
"start": 38,
2803+
"end": 41,
2804+
"name": "foo"
2805+
}
2806+
}
2807+
}
2808+
]
2809+
}
2810+
}
2811+
],
2812+
"sourceType": "script"
2813+
}, {ecmaVersion: 17})
2814+
2815+
test(`async function f() {
2816+
await using instanceof foo;
2817+
}`, {
2818+
"type": "Program",
2819+
"start": 0,
2820+
"end": 52,
2821+
"body": [
2822+
{
2823+
"type": "FunctionDeclaration",
2824+
"start": 0,
2825+
"end": 52,
2826+
"id": {
2827+
"type": "Identifier",
2828+
"start": 15,
2829+
"end": 16,
2830+
"name": "f"
2831+
},
2832+
"expression": false,
2833+
"generator": false,
2834+
"async": true,
2835+
"params": [],
2836+
"body": {
2837+
"type": "BlockStatement",
2838+
"start": 19,
2839+
"end": 52,
2840+
"body": [
2841+
{
2842+
"type": "ExpressionStatement",
2843+
"start": 23,
2844+
"end": 50,
2845+
"expression": {
2846+
"type": "BinaryExpression",
2847+
"start": 23,
2848+
"end": 49,
2849+
"left": {
2850+
"type": "AwaitExpression",
2851+
"start": 23,
2852+
"end": 34,
2853+
"argument": {
2854+
"type": "Identifier",
2855+
"start": 29,
2856+
"end": 34,
2857+
"name": "using"
2858+
}
2859+
},
2860+
"operator": "instanceof",
2861+
"right": {
2862+
"type": "Identifier",
2863+
"start": 46,
2864+
"end": 49,
2865+
"name": "foo"
2866+
}
2867+
}
2868+
}
2869+
]
2870+
}
2871+
}
2872+
],
2873+
"sourceType": "script"
2874+
}, {ecmaVersion: 17})
2875+
2876+
test(`{ let 𠮷 = foo(); }`, {
2877+
"type": "Program",
2878+
"start": 0,
2879+
"end": 19,
2880+
"body": [
2881+
{
2882+
"type": "BlockStatement",
2883+
"start": 0,
2884+
"end": 19,
2885+
"body": [
2886+
{
2887+
"type": "VariableDeclaration",
2888+
"start": 2,
2889+
"end": 17,
2890+
"declarations": [
2891+
{
2892+
"type": "VariableDeclarator",
2893+
"start": 6,
2894+
"end": 16,
2895+
"id": {
2896+
"type": "Identifier",
2897+
"start": 6,
2898+
"end": 8,
2899+
"name": "𠮷"
2900+
},
2901+
"init": {
2902+
"type": "CallExpression",
2903+
"start": 11,
2904+
"end": 16,
2905+
"callee": {
2906+
"type": "Identifier",
2907+
"start": 11,
2908+
"end": 14,
2909+
"name": "foo"
2910+
},
2911+
"arguments": [],
2912+
"optional": false
2913+
}
2914+
}
2915+
],
2916+
"kind": "let"
2917+
}
2918+
]
2919+
}
2920+
],
2921+
"sourceType": "module"
2922+
}, {ecmaVersion: 17, sourceType: "module"})
2923+
2924+
test(`async function f() {
2925+
await using[x];
2926+
}`, {
2927+
"type": "Program",
2928+
"start": 0,
2929+
"end": 40,
2930+
"body": [
2931+
{
2932+
"type": "FunctionDeclaration",
2933+
"start": 0,
2934+
"end": 40,
2935+
"id": {
2936+
"type": "Identifier",
2937+
"start": 15,
2938+
"end": 16,
2939+
"name": "f"
2940+
},
2941+
"expression": false,
2942+
"generator": false,
2943+
"async": true,
2944+
"params": [],
2945+
"body": {
2946+
"type": "BlockStatement",
2947+
"start": 19,
2948+
"end": 40,
2949+
"body": [
2950+
{
2951+
"type": "ExpressionStatement",
2952+
"start": 23,
2953+
"end": 38,
2954+
"expression": {
2955+
"type": "AwaitExpression",
2956+
"start": 23,
2957+
"end": 37,
2958+
"argument": {
2959+
"type": "MemberExpression",
2960+
"start": 29,
2961+
"end": 37,
2962+
"object": {
2963+
"type": "Identifier",
2964+
"start": 29,
2965+
"end": 34,
2966+
"name": "using"
2967+
},
2968+
"property": {
2969+
"type": "Identifier",
2970+
"start": 35,
2971+
"end": 36,
2972+
"name": "x"
2973+
},
2974+
"computed": true,
2975+
"optional": false
2976+
}
2977+
}
2978+
}
2979+
]
2980+
}
2981+
}
2982+
],
2983+
"sourceType": "script"
2984+
}, {ecmaVersion: 17})

0 commit comments

Comments
 (0)