Skip to content

Commit

Permalink
[[FIX]] Correct behavior of singleGroups (#2951)
Browse files Browse the repository at this point in the history
* [[FIX]] Correct behavior of singleGroups

The grouping operator may be necessary if the right-binding power of the
operator preceeding the "(" token is greater than the left-binding power
of the operator immediately following it.

The initial implementation approximated this behavior by using the
readily-accessible left-binding power of both operators. This heuristic
is not always accurate, however as in the following case:

    typeof (a + b);

Here, the necessity of the grouping operator is determined by the
right-binding power of the `typeof` operator.

Extend the Pratt parser to make the right-binding power available to all
null denotation ("nud") functions. Remove an invalid unit test exposed
by this improvement.

* fixup! [[FIX]] Correct behavior of singleGroups
  • Loading branch information
jugglinmike authored and rwaldron committed Jun 27, 2016
1 parent 05d7a31 commit 97fefb7
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 17 deletions.
28 changes: 13 additions & 15 deletions src/jshint.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,10 +821,6 @@ var JSHINT = (function() {
return false;
}

function isBeginOfExpr(prev) {
return !prev.left && prev.arity !== "unary";
}

// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it
// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is
// like .nud except that it is only used on the first token of a statement.
Expand Down Expand Up @@ -881,7 +877,7 @@ var JSHINT = (function() {
left = state.tokens.curr.fud();
} else {
if (state.tokens.curr.nud) {
left = state.tokens.curr.nud();
left = state.tokens.curr.nud(rbp);
} else {
error("E030", state.tokens.curr, state.tokens.curr.id);
}
Expand Down Expand Up @@ -2497,7 +2493,7 @@ var JSHINT = (function() {
return that;
}, 155, true).exps = true;

prefix("(", function() {
prefix("(", function(rbp) {
var pn = state.tokens.next, pn1, i = -1;
var ret, triggerFnExpr, first, last;
var parens = 1;
Expand Down Expand Up @@ -2563,10 +2559,6 @@ var JSHINT = (function() {

first = exprs[0];
last = exprs[exprs.length - 1];

if (!isNecessary) {
isNecessary = preceeding.assign || preceeding.delim;
}
} else {
ret = first = last = exprs[0];

Expand Down Expand Up @@ -2603,7 +2595,8 @@ var JSHINT = (function() {
// first expression *or* the current group contains multiple expressions)
if (!isNecessary && (first.left || first.right || ret.exprs)) {
isNecessary =
(!isBeginOfExpr(preceeding) && first.lbp <= preceeding.lbp) ||
(rbp > first.lbp) ||
(rbp > 0 && rbp === first.lbp) ||
(!isEndOfExpr() && last.lbp < state.tokens.next.lbp);
}

Expand Down Expand Up @@ -2959,12 +2952,17 @@ var JSHINT = (function() {
return funct["(global)"] && !funct["(verb)"];
}

function doTemplateLiteral(left) {
/**
* This function is used as both a null-denotation method *and* a
* left-denotation method, meaning the first parameter is overloaded.
*/
function doTemplateLiteral(leftOrRbp) {
// ASSERT: this.type === "(template)"
// jshint validthis: true
var ctx = this.context;
var noSubst = this.noSubst;
var depth = this.depth;
var left = typeof leftOrRbp === "number" ? null : leftOrRbp;

if (!noSubst) {
while (!end()) {
Expand Down Expand Up @@ -3707,11 +3705,11 @@ var JSHINT = (function() {
});
varstatement.exps = true;

blockstmt("class", function() {
return classdef.call(this, true);
blockstmt("class", function(rbp) {
return classdef.call(this, rbp, true);
});

function classdef(isStatement) {
function classdef(rbp, isStatement) {

/*jshint validthis:true */
if (!state.inES6()) {
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -2710,6 +2710,7 @@ singleGroups.bindingPower.singleExpr = function (test) {
"var v = a % (b / c);",
"var w = a * (b * c);",
"var x = z === (b === c);",
"x = typeof (a + b);",
// Invalid forms:
"var j = 2 * ((3 - 4) - 5) * 6;",
"var l = 2 * ((3 - 4 - 5)) * 6;",
Expand All @@ -2731,7 +2732,6 @@ singleGroups.bindingPower.singleExpr = function (test) {
];

TestRun(test)
.addError(18, "Unnecessary grouping operator.")
.addError(19, "Unnecessary grouping operator.")
.addError(20, "Unnecessary grouping operator.")
.addError(21, "Unnecessary grouping operator.")
Expand All @@ -2748,6 +2748,7 @@ singleGroups.bindingPower.singleExpr = function (test) {
.addError(32, "Unnecessary grouping operator.")
.addError(33, "Unnecessary grouping operator.")
.addError(34, "Unnecessary grouping operator.")
.addError(35, "Unnecessary grouping operator.")
.test(code, { singleGroups: true });

code = [
Expand Down Expand Up @@ -2810,7 +2811,6 @@ singleGroups.multiExpr = function (test) {
];

TestRun(test)
.addError(5, "Unnecessary grouping operator.")
.test(code, { singleGroups: true });

test.done();
Expand Down

0 comments on commit 97fefb7

Please sign in to comment.