Skip to content

Commit

Permalink
[[FEAT]] Implement support for nullish coalescing
Browse files Browse the repository at this point in the history
  • Loading branch information
jugglinmike committed Jan 16, 2021
1 parent 854005d commit d4fe947
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 42 deletions.
23 changes: 23 additions & 0 deletions src/jshint.js
Expand Up @@ -2435,6 +2435,26 @@ var JSHINT = (function() {
return that;
}, andPrecedence);

infix("??", function(context, left, that) {
if (!left.paren && (left.id === "||" || left.id === "&&")) {
error("E024", that, "??");
}

if (!state.inES11()) {
warning("W119", that, "nullish coalescing", "11");
}

increaseComplexityCount();
that.left = left;
var right = that.right = expression(context, 39);

if (!right.paren && (right.id === "||" || right.id === "&&")) {
error("E024", that.right, that.right.id);
}

return that;
}, 39);

// The Exponentiation operator, introduced in ECMAScript 2016
//
// ExponentiationExpression[Yield] :
Expand Down Expand Up @@ -3236,6 +3256,9 @@ var JSHINT = (function() {
// Used to cover a unary expression as the left-hand side of the
// exponentiation operator
(beginsUnaryExpression(ret) && state.tokens.next.id === "**") ||
// Used to cover a logical operator as the right-hand side of the
// nullish coalescing operator
(preceeding.id === "??" && (ret.id === "&&" || ret.id === "||")) ||
// Used to delineate an integer number literal from a dereferencing
// punctuator (otherwise interpreted as a decimal point)
(ret.type === "(number)" &&
Expand Down
9 changes: 8 additions & 1 deletion src/lex.js
Expand Up @@ -249,7 +249,6 @@ Lexer.prototype = {
case "]":
case ":":
case "~":
case "?":
return {
type: Token.Punctuator,
value: ch1
Expand Down Expand Up @@ -288,6 +287,14 @@ Lexer.prototype = {
// Peek more characters

ch2 = this.peek(1);

if (ch1 === "?") {
return {
type: Token.Punctuator,
value: ch2 === "?" ? "??" : "?"
};
}

ch3 = this.peek(2);
ch4 = this.peek(3);

Expand Down
3 changes: 2 additions & 1 deletion src/options.js
Expand Up @@ -1052,7 +1052,8 @@ exports.val = {
* 10](https://www.ecma-international.org/ecma-262/10.0/index.html).
* Notable additions: optional catch bindings.
* - `11` - To enable language features introduced by ECMAScript 11. Notable
* additions: "export * as ns from 'module'".
* additions: "export * as ns from 'module'" and the nullish coalescing
* operator.
*/
esversion: 5
};
Expand Down
40 changes: 0 additions & 40 deletions tests/test262/expectations.txt
Expand Up @@ -9060,46 +9060,6 @@ test/language/expressions/class/private-static-method-brand-check-multiple-evalu
test/language/expressions/class/private-static-method-brand-check-multiple-evaluations-of-class-factory.js(strict mode)
test/language/expressions/class/private-static-setter-multiple-evaluations-of-class-factory.js(default)
test/language/expressions/class/private-static-setter-multiple-evaluations-of-class-factory.js(strict mode)
test/language/expressions/coalesce/abrupt-is-a-short-circuit.js(default)
test/language/expressions/coalesce/abrupt-is-a-short-circuit.js(strict mode)
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js(default)
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js(strict mode)
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js(default)
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js(strict mode)
test/language/expressions/coalesce/chainable-with-bitwise-and.js(default)
test/language/expressions/coalesce/chainable-with-bitwise-and.js(strict mode)
test/language/expressions/coalesce/chainable-with-bitwise-or.js(default)
test/language/expressions/coalesce/chainable-with-bitwise-or.js(strict mode)
test/language/expressions/coalesce/chainable-with-bitwise-xor.js(default)
test/language/expressions/coalesce/chainable-with-bitwise-xor.js(strict mode)
test/language/expressions/coalesce/chainable.js(default)
test/language/expressions/coalesce/chainable.js(strict mode)
test/language/expressions/coalesce/follows-null.js(default)
test/language/expressions/coalesce/follows-null.js(strict mode)
test/language/expressions/coalesce/follows-undefined.js(default)
test/language/expressions/coalesce/follows-undefined.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-0.js(default)
test/language/expressions/coalesce/short-circuit-number-0.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-42.js(default)
test/language/expressions/coalesce/short-circuit-number-42.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-empty-string.js(default)
test/language/expressions/coalesce/short-circuit-number-empty-string.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-false.js(default)
test/language/expressions/coalesce/short-circuit-number-false.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-object.js(default)
test/language/expressions/coalesce/short-circuit-number-object.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-string.js(default)
test/language/expressions/coalesce/short-circuit-number-string.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-symbol.js(default)
test/language/expressions/coalesce/short-circuit-number-symbol.js(strict mode)
test/language/expressions/coalesce/short-circuit-number-true.js(default)
test/language/expressions/coalesce/short-circuit-number-true.js(strict mode)
test/language/expressions/coalesce/short-circuit-prevents-evaluation.js(default)
test/language/expressions/coalesce/short-circuit-prevents-evaluation.js(strict mode)
test/language/expressions/coalesce/tco-pos-null.js(strict mode)
test/language/expressions/coalesce/tco-pos-undefined.js(strict mode)
test/language/expressions/conditional/coalesce-expr-ternary.js(default)
test/language/expressions/conditional/coalesce-expr-ternary.js(strict mode)
test/language/expressions/logical-assignment/lgcl-and-assignment-operator-bigint.js(default)
test/language/expressions/logical-assignment/lgcl-and-assignment-operator-bigint.js(strict mode)
test/language/expressions/logical-assignment/lgcl-and-assignment-operator-lhs-before-rhs.js(default)
Expand Down
26 changes: 26 additions & 0 deletions tests/unit/options.js
Expand Up @@ -2735,6 +2735,12 @@ exports.maxcomplexity = function (test) {
TestRun(test)
.test(src, { es3: true });

TestRun(test, "nullish coalescing operator")
.addError(1, 11, "This function's cyclomatic complexity is too high. (2)")
.test([
"function f() { 0 ?? 0; }"
], { esversion: 11, expr: true, maxcomplexity: 1 });

test.done();
};

Expand Down Expand Up @@ -3669,6 +3675,26 @@ singleGroups.destructuringAssign = function (test) {
test.done();
};

singleGroups.nullishCoalescing = function (test) {
TestRun(test)
.addError(1, 1, "Unnecessary grouping operator.")
.addError(2, 6, "Unnecessary grouping operator.")
.test([
"(0) ?? 0;",
"0 ?? (0);"
], { singleGroups: true, expr: true, esversion: 11 });

TestRun(test)
.test([
"0 ?? (0 || 0);",
"(0 ?? 0) || 0;",
"0 ?? (0 && 0);",
"(0 ?? 0) && 0;"
], { singleGroups: true, expr: true, esversion: 11 });

test.done();
};

exports.elision = function (test) {
var code = [
"var a = [1,,2];",
Expand Down
73 changes: 73 additions & 0 deletions tests/unit/parser.js
Expand Up @@ -10267,3 +10267,76 @@ exports.parensAfterDeclaration = function (test) {

test.done();
};

exports.nullishCoalescing = {};

exports.nullishCoalescing.positive = function(test) {
TestRun(test, "requires esversion: 11")
.addError(1, 3, "'nullish coalescing' is only available in ES11 (use 'esversion: 11').")
.test([
"0 ?? 0;"
], { esversion: 10, expr: true });

TestRun(test, "does not stand alone")
.addError(1, 6, "Expected an assignment or function call and instead saw an expression.")
.test([
"0 ?? 0;"
], { esversion: 11 });

TestRun(test, "precedence with bitwise OR")
.test([
"0 | 0 ?? 0;"
], { esversion: 11, expr: true });

TestRun(test, "precedence with conditional expression")
.test([
"0 ?? 0 ? 0 ?? 0 : 0 ?? 0;"
], { esversion: 11, expr: true });

TestRun(test, "precedence with expression")
.test([
"0 ?? 0, 0 ?? 0;"
], { esversion: 11, expr: true });

TestRun(test, "covered")
.test([
"0 || (0 ?? 0);",
"(0 || 0) ?? 0;",
"(0 ?? 0) || 0;",
"0 ?? (0 || 0);",
"0 && (0 ?? 0);",
"(0 && 0) ?? 0;",
"(0 ?? 0) && 0;",
"0 ?? (0 && 0);"
], { esversion: 11, expr: true });

test.done();
};

exports.nullishCoalescing.negative = function(test) {
TestRun(test, "precedence with logical OR")
.addError(1, 8, "Unexpected '??'.")
.test([
"0 || 0 ?? 0;"
], { esversion: 11, expr: true });

TestRun(test, "precedence with logical OR")
.addError(1, 8, "Unexpected '||'.")
.test([
"0 ?? 0 || 0;"
], { esversion: 11, expr: true });

TestRun(test, "precedence with logical AND")
.addError(1, 8, "Unexpected '??'.")
.test([
"0 && 0 ?? 0;"
], { esversion: 11, expr: true });

TestRun(test, "precedence with logical AND")
.addError(1, 8, "Unexpected '&&'.")
.test([
"0 ?? 0 && 0;"
], { esversion: 11, expr: true });

test.done();
};

0 comments on commit d4fe947

Please sign in to comment.