Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[[FEAT]] Implement support for nullish coalescing #3516

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
};