Repository: jshint/jshint
Pull request #2069 (2015-01-02 07:00:52)

Add additional test cases to increase code coverage
This should hit additional corner cases in lex.js, trivially increasing code coverage in scanNumericLiteral() and scanEscapeSequence(). Please verify that the test case behavior itself is correct since I'm asserting the actual behavior not necessarily the expected behavior for these cases.


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3e69e2ff..9ad155c3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,7 +7,8 @@ to a snippet, graffiti outside of Anton's apartment, etc.
 
 However, before sending a patch, please make sure that the following applies:
 
-* Your commit message follows the [Commit Message Guidelines](#commit-message-guidelines).
+* Your commit message links to that issue.
+* Your commit message is very descriptive ([example](https://github.com/jshint/jshint/commit/5751c5ed249b7a035758a3ae876cfa1a360fd144)).
 * Your patch doesn't have useless merge commits.
 * Your coding style is similar to ours (see below).
 * Your patch is 100% tested. We don't accept any test regressions.
@@ -96,78 +97,3 @@ token.isPunctuator(";") && report.addWarning("W001");
 if (token.isPunctuator(";"))
   report.addWarning("W001");
 ```
-
-Commit Message Guidelines
--------------------------
-
-### Overview
-
-Commit messages are written in a simple format which clearly describes the purpose of a change.
-
-The format in general should look like this:
-
-```
-[[TYPE]] <Short description>
-<Blank line>
-
-<Body / Detailed description>
-
-<Footer>
-```
-
-Line lengths in commit messages are not strict, but good commit messages should have headers of no
-more than 60 characters, and bodies/footers wrapped at 100 columns. This renders nicely on Github's
-UI.
-
-### Header
-
-The first line is the commit message header, which will indicate the type of change, and a general
-description of the change. This should fit within 60 characters, ideally. For instance:
-
-```
-[[FIX]] Ignore "nocomma" when parsing object literals
-```
-
-The title `[[FIX]]` indicates that the change is a bugfix, while the remainder clarifies what the
-change actually contains.
-
-Several commit types are used by jshint:
-
-1. `[[FIX]]` --- Commit fixes a bug or regression
-2. `[[FEAT]]` --- Commit introduces new functionality
-3. `[[DOCS]]` --- Commit modifies documentation. Docs commits should only touch comments in source code, or scripts and assets which are used to generate the documentation.
-
-### Body
-
-`<Body>` is a detailed commit message explaining exactly what has changed, and a summary of the
-reason why. Lines in the body should be wrapped to 100 characters for best rendering.
-
-For a historical example, see this [example](https://github.com/jshint/jshint/commit/                                     5751c5ed249b7a035758a3ae876cfa1a360fd144) 
-
-### Footer
-
-`<Footer>` contains a description of any breaking changes, no matter how subtle, as well as a list
-of issues affected or fixed by this commit. Lines in the footer should be wrapped to 100 characters
-for best rendering.
-
-For instance:
-
-```
-[[FEAT]] Enable `norecurs` option by default
-
-Commit 124124a7f introduced an option which forbids recursion. We liked it so much, we've enabled
-it by default.
-
-BREAKING CHANGE:
-
-This change will break the CI builds of many applications and frameworks.
-
-In order to work around this issue, you will need to re-engineer your applications and frameworks
-to avoid making recursive calls. Use Arrays as stacks rather than relying on the VM call stack.
-
-Fixes #1000009
-Closes #888888
-Closes #77777
-```
-
-
diff --git a/dist/jshint-rhino.js b/dist/jshint-rhino.js
index d8d56ce7..5b5ac404 100755
--- a/dist/jshint-rhino.js
+++ b/dist/jshint-rhino.js
@@ -4988,7 +4988,7 @@ exports.bool = {
     /**
      * This option requires all `for in` loops to filter object's items. The
      * for in statement allows for looping through the names of all of the
-     * properties of an object including those inherited through the prototype
+     * properties of an object including those inherited throught the prototype
      * chain. This behavior can lead to unexpected items in your object so it
      * is generally safer to always filter inherited properties out as shown in
      * the example:
@@ -6675,12 +6675,10 @@ exports.mocha = {
   beforeEach  : false,
   afterEach   : false,
   // TDD
-  suite         : false,
-  test          : false,
-  setup         : false,
-  teardown      : false,
-  suiteSetup    : false,
-  suiteTeardown : false
+  suite       : false,
+  test        : false,
+  setup       : false,
+  teardown    : false
 };
 
 exports.jasmine = {
diff --git a/dist/jshint.js b/dist/jshint.js
index ffd1144d..840ad4aa 100644
--- a/dist/jshint.js
+++ b/dist/jshint.js
@@ -4986,7 +4986,7 @@ exports.bool = {
     /**
      * This option requires all `for in` loops to filter object's items. The
      * for in statement allows for looping through the names of all of the
-     * properties of an object including those inherited through the prototype
+     * properties of an object including those inherited throught the prototype
      * chain. This behavior can lead to unexpected items in your object so it
      * is generally safer to always filter inherited properties out as shown in
      * the example:
@@ -6673,12 +6673,10 @@ exports.mocha = {
   beforeEach  : false,
   afterEach   : false,
   // TDD
-  suite         : false,
-  test          : false,
-  setup         : false,
-  teardown      : false,
-  suiteSetup    : false,
-  suiteTeardown : false
+  suite       : false,
+  test        : false,
+  setup       : false,
+  teardown    : false
 };
 
 exports.jasmine = {
diff --git a/src/jshint.js b/src/jshint.js
index 6fd9eecf..0d0f3440 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -1169,10 +1169,14 @@ var JSHINT = (function () {
     var x = symbol(s, 42);
 
     x.led = function (left) {
+      if (!state.option.esnext) {
+        warning("W119", state.tokens.curr, "arrow function syntax (=>)");
+      }
+
       nobreaknonadjacent(state.tokens.prev, state.tokens.curr);
 
       this.left = left;
-      this.right = doFunction(undefined, undefined, false, { loneArg: left });
+      this.right = doFunction(undefined, undefined, false, left);
       return this;
     };
     return x;
@@ -1451,14 +1455,6 @@ var JSHINT = (function () {
       }
     } else {
       error("E030", state.tokens.next, state.tokens.next.value);
-
-      // The token should be consumed after a warning is issued so the parser
-      // can continue as though an identifier were found. The semicolon token
-      // should not be consumed in this way so that the parser interprets it as
-      // a statement delimeter;
-      if (state.tokens.next.id !== ";") {
-        advance();
-      }
     }
   }
 
@@ -1881,18 +1877,6 @@ var JSHINT = (function () {
       var f;
       var block;
 
-      // If this identifier is the lone parameter to a shorthand "fat arrow"
-      // function definition, i.e.
-      //
-      //     x => x;
-      //
-      // ...it should not be considered as a variable in the current scope. It
-      // will be added to the scope of the new function when the next token is
-      // parsed, so it can be safely ignored for now.
-      if (state.tokens.next.id === "=>") {
-        return this;
-      }
-
       if (typeof s === "function") {
         // Protection against accidental inheritance.
         s = undefined;
@@ -2443,8 +2427,8 @@ var JSHINT = (function () {
       }
       if (!left.identifier && left.id !== "." && left.id !== "[" &&
           left.id !== "(" && left.id !== "&&" && left.id !== "||" &&
-          left.id !== "?" && !(state.option.esnext && left["(name)"])) {
-        warning("W067", that);
+          left.id !== "?" && !(state.option.esnext && left.id === "=>")) {
+        warning("W067", left);
       }
     }
 
@@ -2454,11 +2438,13 @@ var JSHINT = (function () {
 
   prefix("(", function () {
     var bracket, brackets = [];
-    var pn = state.tokens.next, pn1, i = -1;
+    var pn, pn1, i = 0;
     var ret, triggerFnExpr;
     var parens = 1;
 
     do {
+      pn = peek(i);
+
       if (pn.value === "(") {
         parens += 1;
       } else if (pn.value === ")") {
@@ -2466,27 +2452,19 @@ var JSHINT = (function () {
       }
 
       i += 1;
-      pn1 = pn;
-      pn = peek(i);
-    } while (!(parens === 0 && pn1.value === ")") &&
-             pn.value !== ";" && pn.type !== "(end)");
+      pn1 = peek(i);
+    } while (!(parens === 0 && pn.value === ")") &&
+             pn1.value !== "=>" && pn1.value !== ";" && pn1.type !== "(end)");
 
     if (state.tokens.next.id === "function") {
       triggerFnExpr = state.tokens.next.immed = true;
     }
 
-    // If the balanced grouping operator is followed by a "fat arrow", the
-    // current token marks the beginning of a "fat arrow" function and parsing
-    // should proceed accordingly.
-    if (pn.value === "=>") {
-      return doFunction(null, null, null, { parsedParen: true });
-    }
-
     var exprs = [];
 
     if (state.tokens.next.id !== ")") {
       for (;;) {
-        if (pn.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) {
+        if (pn1.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) {
           bracket = state.tokens.next;
           bracket.left = destructuringExpression();
           brackets.push(bracket);
@@ -2704,25 +2682,40 @@ var JSHINT = (function () {
     return id;
   }
 
-  function functionparams(fatarrow) {
-    var next;
+  function functionparams(parsed) {
+    var curr, next;
     var params = [];
     var ident;
     var tokens = [];
     var t;
     var pastDefault = false;
-    var loneArg = fatarrow && fatarrow.loneArg;
 
-    if (loneArg && loneArg.identifier === true) {
-      addlabel(loneArg.value, { type: "unused", token: loneArg });
-      return [loneArg];
+    if (parsed) {
+      if (Array.isArray(parsed)) {
+        for (var i in parsed) {
+          curr = parsed[i];
+          if (curr.value === "...") {
+            if (!state.option.esnext) {
+              warning("W119", curr, "spread/rest operator");
+            }
+            continue;
+          } else if (curr.value !== ",") {
+            params.push(curr.value);
+            addlabel(curr.value, { type: "unused", token: curr, param: true });
+          }
+        }
+        return params;
+      } else {
+        if (parsed.identifier === true) {
+          addlabel(parsed.value, { type: "unused", token: parsed, param: true });
+          return [parsed];
+        }
+      }
     }
 
     next = state.tokens.next;
 
-    if (!fatarrow || !fatarrow.parsedParen) {
-      advance("(");
-    }
+    advance("(");
 
     if (state.tokens.next.id === ")") {
       advance(")");
@@ -2848,18 +2841,7 @@ var JSHINT = (function () {
     };
   }
 
-  /**
-   * @param {Object} [fatarrow] In the case that the function being parsed
-   *                            takes the "fat arrow" form, this object will
-   *                            contain details about the in-progress parsing
-   *                            operation.
-   * @param {Token} [fatarrow.loneArg] The argument to the function in cases
-   *                                   where it was defined using the single-
-   *                                   argument shorthand.
-   * @param {bool} [fatarrow.parsedParen] Whether the opening parenthesis has
-   *                                      already been parsed.
-   */
-  function doFunction(name, statement, generator, fatarrow) {
+  function doFunction(name, statement, generator, fatarrowparams) {
     var f;
     var oldOption = state.option;
     var oldIgnored = state.ignored;
@@ -2884,20 +2866,19 @@ var JSHINT = (function () {
       addlabel(name, { type: "function" });
     }
 
-    funct["(params)"] = functionparams(fatarrow);
+    funct["(params)"] = functionparams(fatarrowparams);
     funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]);
 
-    if (fatarrow) {
-      if (!state.option.esnext) {
-        warning("W119", state.tokens.curr, "arrow function syntax (=>)");
-      }
+    // So we parse fat-arrow functions after we encounter =>. So basically
+    // doFunction is called with the left side of => as its last argument.
+    // This means that the parser, at that point, had already added its
+    // arguments to the undefs array and here we undo that.
 
-      if (!fatarrow.loneArg) {
-        advance("=>");
-      }
-    }
+    JSHINT.undefs = _.filter(JSHINT.undefs, function (item) {
+      return !_.contains(_.union(fatarrowparams), item[2]);
+    });
 
-    block(false, true, true, !!fatarrow);
+    block(false, true, true, fatarrowparams ? true : false);
 
     if (!state.option.noyield && generator &&
         funct["(generator)"] !== "yielded") {
diff --git a/src/vars.js b/src/vars.js
index ce63baf1..e3b4d709 100644
--- a/src/vars.js
+++ b/src/vars.js
@@ -675,12 +675,10 @@ exports.mocha = {
   beforeEach  : false,
   afterEach   : false,
   // TDD
-  suite         : false,
-  test          : false,
-  setup         : false,
-  teardown      : false,
-  suiteSetup    : false,
-  suiteTeardown : false
+  suite       : false,
+  test        : false,
+  setup       : false,
+  teardown    : false
 };
 
 exports.jasmine = {


Test patch:
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 7baa03ad..e9334f13 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -312,6 +312,10 @@ exports.numbers = function (test) {
     "var j = 1e-10;", // GH-821
     "var k = 0o1234567;",
     "var l = 0b101;",
+    "var m = 0x;",
+    "var n = 09;",
+    "var o = 1e-A;",
+    "var p = 1/;",
   ];
 
   TestRun(test)
@@ -325,6 +329,11 @@ exports.numbers = function (test) {
     .addError(9, "A dot following a number can be confused with a decimal point.")
     .addError(11, "'Octal integer literal' is only available in ES6 (use esnext option).")
     .addError(12, "'Binary integer literal' is only available in ES6 (use esnext option).")
+    .addError(13, "Bad number '0x'.")
+    .addError(15, "Unexpected '1'.")
+    .addError(16, "Expected an identifier and instead saw ';'.")
+    .addError(16, "Expected an identifier and instead saw 'var'.")
+    .addError(16, "Missing semicolon.")
     .test(code, {es3: true});
 
   // Octals are prohibited in strict mode.
@@ -529,14 +538,15 @@ exports.strings = function (test) {
     "var b = \'\\g\';",
     "var c = '\\u0022\\u0070\\u005C';",
     "var e = '\\x6b..\\x6e';",
-    "var f = 'ax"
+    "var f = '\\b\\f\\n\\/';",
+    "var g = 'ax",
   ];
 
   var run = TestRun(test)
     .addError(1, "Control character in string: <non-printable>.", {character: 10})
     .addError(1, "This character may get silently deleted by one or more browsers.")
-    .addError(5, "Unclosed string.")
-    .addError(5, "Missing semicolon.");
+    .addError(6, "Unclosed string.")
+    .addError(6, "Missing semicolon.");
   run.test(code, {es3: true});
   run.test(code, {}); // es5
   run.test(code, {esnext: true});
@@ -820,6 +830,8 @@ exports["destructuring var in function scope"] = function (test) {
     .addError(9,  "'a' is already defined.")
     .addError(9,  "'bar' is already defined.")
     .addError(10,  "Expected an identifier and instead saw '1'.")
+    .addError(10,  "Expected ',' and instead saw '1'.")
+    .addError(10,  "Expected an identifier and instead saw ']'.")
     .addError(11, "Expected ',' and instead saw ';'.")
     .addError(11, "'a' is already defined.")
     .addError(11, "'b' is already defined.")
@@ -994,6 +1006,8 @@ exports["destructuring var errors"] = function (test) {
 
   TestRun(test)
     .addError(9,  "Expected an identifier and instead saw '1'.")
+    .addError(9,  "Expected ',' and instead saw '1'.")
+    .addError(9,  "Expected an identifier and instead saw ']'.")
     .addError(10, "Expected ',' and instead saw ';'.")
     .addError(11, "Expected ']' to match '[' from line 11 and instead saw ';'.")
     .addError(11, "Missing semicolon.")
@@ -1213,6 +1227,8 @@ exports["destructuring const errors"] = function (test) {
     .addError(2, "const 'b' has already been declared.")
     .addError(2, "const 'c' has already been declared.")
     .addError(3, "Expected an identifier and instead saw '1'.")
+    .addError(3, "Expected ',' and instead saw '1'.")
+    .addError(3, "Expected an identifier and instead saw ']'.")
     .addError(4, "Expected ',' and instead saw ';'.")
     .addError(5, "Expected ']' to match '[' from line 5 and instead saw ';'.")
     .addError(5, "Missing semicolon.")
@@ -3693,53 +3709,6 @@ exports["fat arrows support"] = function (test) {
   test.done();
 };
 
-exports["fat arrow nested function scoping"] = function (test) {
-  var code = [
-    "(() => {",
-    "  for (var i = 0; i < 10; i++) {",
-    "    var x;",
-    "  }",
-    "  var arrow = (x) => {",
-    "    return x;",
-    "  };",
-    "  var arrow2 = (x) => x;",
-    "  arrow();",
-    "  arrow2();",
-    "})();",
-    "(function() {",
-    "  for (var i = 0; i < 10; i++) {",
-    "    var x;",
-    "  }",
-    "  var arrow = (x) => {",
-    "    return x;",
-    "  };",
-    "  var arrow2 = (x) => x;",
-    "  arrow();",
-    "  arrow2();",
-    "})();"
-  ];
-
-  TestRun(test)
-    .test(code, {esnext: true});
-
-  test.done();
-};
-
-exports["default arguments in fat arrow functions"] = function (test) {
-  TestRun(test)
-    .test("(x = 0) => { return x; };", { expr: true, unused: true, esnext: true });
-
-  test.done();
-};
-
-exports["expressions in place of arrow function parameters"] = function (test) {
-  TestRun(test)
-    .addError(1, "Expected an identifier and instead saw '1'.")
-    .test("(1) => {};", { expr: true, esnext: true });
-
-  test.done();
-};
-
 var conciseMethods = exports.conciseMethods = {};
 
 conciseMethods.basicSupport = function (test) {