Permalink
Browse files

[[FIX]] Correct interpretation of ASI (#3045)

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 9803e11eb61bdaa9e7571b5674f5dc37eb792a3e
Showing with 55 additions and 9 deletions.
  1. +14 −8 src/jshint.js
  2. +41 −1 tests/unit/parser.js
@@ -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) {
@@ -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);
}
@@ -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);

@@ -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;
@@ -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);
@@ -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) ||
@@ -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;",
@@ -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 = () => {};",

0 comments on commit 9803e11

Please sign in to comment.