Skip to content

Commit

Permalink
[[FIX]] Correct interpretation of ASI (jshint#3045)
Browse files Browse the repository at this point in the history
Previously, the following operators were incorrectly interpreted as
"infix" operators:

- `++`
- `--`
- `yield`

The internal `isInfix` function tended to obscure this error because it
identified "infix" tokens via a boolean logic expression that acted as a
blacklist. This expression was difficult to understand and hostile to
future extensions for new language features (because future tokens could
be mistakenly implemented as "infix" operators).

Update all "infix" operator token definitions to explicitly define the
`infix` property, obviating the need for the internal `isInfix`
function. Ensure that the aforementioned tokens are not interpreted as
"infix" operators, This corrects the behavior of the internal
`isEndOfExpr` function, which is appreciable when linting code that
relies on automatic semicolon insertion.
  • Loading branch information
jugglinmike authored and rwaldron committed Oct 17, 2016
1 parent bec152c commit 9803e11
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 9 deletions.
22 changes: 14 additions & 8 deletions src/jshint.js
Expand Up @@ -805,12 +805,15 @@ var JSHINT = (function() {
}
}

function isInfix(token) {
return token.infix ||
(!token.identifier && !token.template && !!token.led) ||
// Although implemented as an Identifier, the `yield` keyword behaves as
// an operator when it appears in the body of a generator function.
(token.id === "yield" && !!state.funct["(generator)"]);
/**
* Determine whether a given token is an operator.
*
* @param {token} token
*
* @returns {boolean}
*/
function isOperator(token) {
return token.first || token.right || token.left || token.id === "yield";
}

function isEndOfExpr(curr, next) {
Expand All @@ -822,7 +825,7 @@ var JSHINT = (function() {
if (next.id === ";" || next.id === "}" || next.id === ":") {
return true;
}
if (isInfix(next) === isInfix(curr) || curr.ltBoundary === "after" ||
if (next.infix === curr.infix || curr.ltBoundary === "after" ||
next.ltBoundary === "before") {
return curr.line !== startLine(next);
}
Expand Down Expand Up @@ -1165,6 +1168,7 @@ var JSHINT = (function() {
function application(s) {
var x = symbol(s, 42);

x.infix = true;
x.led = function(left) {
nobreaknonadjacent(state.tokens.prev, state.tokens.curr);

Expand All @@ -1178,6 +1182,7 @@ var JSHINT = (function() {
function relation(s, f) {
var x = symbol(s, 100);

x.infix = true;
x.led = function(left) {
nobreaknonadjacent(state.tokens.prev, state.tokens.curr);
this.left = left;
Expand Down Expand Up @@ -1400,6 +1405,7 @@ var JSHINT = (function() {
function bitwise(s, f, p) {
var x = symbol(s, p);
reserveName(x);
x.infix = true;
x.led = (typeof f === "function") ? f : function(left) {
if (state.option.bitwise) {
warning("W016", this, this.id);
Expand Down Expand Up @@ -2590,7 +2596,7 @@ var JSHINT = (function() {
// The operator may be necessary to override the default binding power of
// neighboring operators (whenever there is an operator in use within the
// first expression *or* the current group contains multiple expressions)
if (!isNecessary && (isInfix(first) || first.right || ret.exprs)) {
if (!isNecessary && (isOperator(first) || ret.exprs)) {
isNecessary =
(rbp > first.lbp) ||
(rbp > 0 && rbp === first.lbp) ||
Expand Down
42 changes: 41 additions & 1 deletion tests/unit/parser.js
Expand Up @@ -5226,7 +5226,8 @@ exports["regression test for crash from GH-964"] = function (test) {
test.done();
};

exports["automatic comma insertion GH-950"] = function (test) {
exports.ASI = {};
exports.ASI.gh950 = function (test) {
var code = [
"var a = b",
"instanceof c;",
Expand Down Expand Up @@ -5289,6 +5290,45 @@ exports["automatic comma insertion GH-950"] = function (test) {
test.done();
};

// gh-3037 - weird behaviour (yield related)
// https://github.com/jshint/jshint/issues/3037
exports.ASI.followingYield = function (test) {
var code = [
"function* g() {",
" void 0",
" yield;",
"}"
];

TestRun(test)
.addError(2, "Missing semicolon.")
.test(code, { esversion: 6 });

TestRun(test)
.test(code, { esversion: 6, asi: true });

test.done();
};

exports.ASI.followingPostfix = function (test) {
var code = [
"x++",
"void 0;",
"x--",
"void 0;"
];

TestRun(test)
.addError(1, "Missing semicolon.")
.addError(3, "Missing semicolon.")
.test(code);

TestRun(test)
.test(code, { asi: true });

test.done();
};

exports["fat arrows support"] = function (test) {
var code = [
"let empty = () => {};",
Expand Down

0 comments on commit 9803e11

Please sign in to comment.