Skip to content

Commit 637a0a2

Browse files
committed
add nullish coalescing
1 parent e30802f commit 637a0a2

File tree

6 files changed

+572
-8
lines changed

6 files changed

+572
-8
lines changed

acorn-loose/src/expression.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ lp.parseExprOp = function(left, start, minPrec, noIn, indent, line) {
102102
let rightStart = this.storeCurrentPos()
103103
node.right = this.parseExprOp(this.parseMaybeUnary(false), rightStart, prec, noIn, indent, line)
104104
}
105-
this.finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression")
105+
this.finishNode(node, /&&|\|\||\?\?/.test(node.operator) ? "LogicalExpression" : "BinaryExpression")
106106
return this.parseExprOp(node, start, minPrec, noIn, indent, line)
107107
}
108108
}

acorn/src/expression.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ pp.parseExprOps = function(noIn, refDestructuringErrors) {
169169
let startPos = this.start, startLoc = this.startLoc
170170
let expr = this.parseMaybeUnary(refDestructuringErrors, false)
171171
if (this.checkExpressionErrors(refDestructuringErrors)) return expr
172-
return expr.start === startPos && expr.type === "ArrowFunctionExpression" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, noIn)
172+
return expr.start === startPos && expr.type === "ArrowFunctionExpression" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, noIn, 0)
173173
}
174174

175175
// Parse binary operators with the operator precedence parsing
@@ -178,17 +178,28 @@ pp.parseExprOps = function(noIn, refDestructuringErrors) {
178178
// defer further parser to one of its callers when it encounters an
179179
// operator that has a lower precedence than the set it is parsing.
180180

181-
pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn) {
181+
// shortCircuitOps:
182+
// * 1 = only `||` and `&&` are allowed
183+
// * 2 = only `??` is allowed
184+
// * others = all
185+
186+
pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn, shortCircuitOps) {
182187
let prec = this.type.binop
183188
if (prec != null && (!noIn || this.type !== tt._in)) {
189+
let logical = this.type === tt.logicalOR || this.type === tt.logicalAND
190+
let coalesce = this.type === tt.coalesce
191+
if (shortCircuitOps === 1 && coalesce || shortCircuitOps === 2 && logical) {
192+
this.raiseRecoverable(this.start, "Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses")
193+
}
194+
shortCircuitOps = shortCircuitOps || (logical ? 1 : coalesce ? 2 : 0)
195+
184196
if (prec > minPrec) {
185-
let logical = this.type === tt.logicalOR || this.type === tt.logicalAND
186197
let op = this.value
187198
this.next()
188199
let startPos = this.start, startLoc = this.startLoc
189-
let right = this.parseExprOp(this.parseMaybeUnary(null, false), startPos, startLoc, prec, noIn)
190-
let node = this.buildBinary(leftStartPos, leftStartLoc, left, right, op, logical)
191-
return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn)
200+
let right = this.parseExprOp(this.parseMaybeUnary(null, false), startPos, startLoc, prec, noIn, shortCircuitOps)
201+
let node = this.buildBinary(leftStartPos, leftStartLoc, left, right, op, logical || coalesce)
202+
return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn, shortCircuitOps)
192203
}
193204
}
194205
return left

acorn/src/tokenize.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,14 @@ pp.readToken_eq_excl = function(code) { // '=!'
287287
return this.finishOp(code === 61 ? tt.eq : tt.prefix, 1)
288288
}
289289

290+
pp.readToken_question = function() { // '?'
291+
if (this.options.ecmaVersion >= 11) {
292+
let next = this.input.charCodeAt(this.pos + 1)
293+
if (next === 63) return this.finishOp(tt.coalesce, 2)
294+
}
295+
return this.finishOp(tt.question, 1)
296+
}
297+
290298
pp.getTokenFromCode = function(code) {
291299
switch (code) {
292300
// The interpretation of a dot depends on whether it is followed
@@ -304,7 +312,6 @@ pp.getTokenFromCode = function(code) {
304312
case 123: ++this.pos; return this.finishToken(tt.braceL)
305313
case 125: ++this.pos; return this.finishToken(tt.braceR)
306314
case 58: ++this.pos; return this.finishToken(tt.colon)
307-
case 63: ++this.pos; return this.finishToken(tt.question)
308315

309316
case 96: // '`'
310317
if (this.options.ecmaVersion < 6) break
@@ -354,6 +361,9 @@ pp.getTokenFromCode = function(code) {
354361
case 61: case 33: // '=!'
355362
return this.readToken_eq_excl(code)
356363

364+
case 63: // '?'
365+
return this.readToken_question()
366+
357367
case 126: // '~'
358368
return this.finishOp(tt.prefix, 1)
359369
}

acorn/src/tokentype.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export const types = {
108108
star: binop("*", 10),
109109
slash: binop("/", 10),
110110
starstar: new TokenType("**", {beforeExpr: true}),
111+
coalesce: binop("??", 1),
111112

112113
// Keyword token types.
113114
_break: kw("break"),

test/run.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require("./tests-optional-catch-binding.js");
1717
require("./tests-bigint.js");
1818
require("./tests-dynamic-import.js");
19+
require("./tests-nullish-coalescing.js");
1920
var acorn = require("../acorn")
2021
var acorn_loose = require("../acorn-loose")
2122

0 commit comments

Comments
 (0)