Repository: eslint/eslint
Pull request #8232 (2017-03-11 19:33:37)

Chore: convert Traverser to ES6 class (refs #7849)
**What is the purpose of this pull request? (put an "X" next to item)**

[x] Other, please explain:

**What changes did you make? (Give an overview)**

This converts `Traverser` (our wrapper around estraverse) into an ES6 class. Since `Traverser` is not a public API, this should not be a breaking change.

This also adds tests for `Traverser` (there were no tests before).

Based on `npm run perf` numbers, this improves end-to-end performance by 4%. :tada:

**Is there anything you'd like reviewers to focus on?**

Looking at the history of the `Traverser` wrapper, I see that it was introduced to fix https://github.com/eslint/eslint/issues/5476 (a bug which broke a lot of projects that used babel-eslint). I'm not sure I understand the exact circumstances that caused that bug, but we should make sure this change isn't doing anything that will cause the bug to reappear. Pinging @hzoo since he probably knows more about this.

The regression build reports no errors with this change.

mention-bot: @not-an-aardvark, thanks for your PR! By analyzing the history of the files in this pull request, we identified @nzakas and @mysticatea to be potential reviewers.
eslintbot: LGTM
not-an-aardvark: Summary of the changes that are observable:

* Calling `Traverser` without `new` will throw an error.
* The `traverse` method of `Traverser` instances is now on `Traverser.prototype` (previously, a new function was created for each `Traverser` instance).
* `Traverser.getKeys` and `Traverser.prototype.traverse` are now non-enumerable properties.
* Calling `new Traverser.getKeys(node)` will now throw an error.

Patch:
diff --git a/Makefile.js b/Makefile.js
index 3c57a494e..24151b884 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -740,7 +740,6 @@ target.gensite = function(prereleaseVersion) {
     // 13. Update demos, but only for non-prereleases
     if (!prereleaseVersion) {
         cp("-f", "build/eslint.js", `${SITE_DIR}js/app/eslint.js`);
-        cp("-f", "conf/eslint.json", `${SITE_DIR}js/app/eslint.json`);
     }
 
     // 14. Create Example Formatter Output Page
diff --git a/docs/developer-guide/code-path-analysis.md b/docs/developer-guide/code-path-analysis.md
index bc855c316..7762bc5d6 100644
--- a/docs/developer-guide/code-path-analysis.md
+++ b/docs/developer-guide/code-path-analysis.md
@@ -26,7 +26,7 @@ This has references of both the initial segment and the final segments of a code
 
 `CodePath` has the following properties:
 
-* `id` (`string`) - An unique string. Respective rules can use `id` to save additional information for each code path.
+* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path.
 * `initialSegment` (`CodePathSegment`) - The initial segment of this code path.
 * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown.
 * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned.
@@ -43,7 +43,7 @@ Difference from doubly linked list is what there are forking and merging (the ne
 
 `CodePathSegment` has the following properties:
 
-* `id` (`string`) - An unique string. Respective rules can use `id` to save additional information for each segment.
+* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment.
 * `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing.
 * `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing.
 * `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`.
diff --git a/docs/developer-guide/code-path-analysis/README.md b/docs/developer-guide/code-path-analysis/README.md
index 62f60cf68..c283d51bf 100644
--- a/docs/developer-guide/code-path-analysis/README.md
+++ b/docs/developer-guide/code-path-analysis/README.md
@@ -26,7 +26,7 @@ This has references of both the initial segment and the final segments of a code
 
 `CodePath` has the following properties:
 
-* `id` (`string`) - An unique string. Respective rules can use `id` to save additional information for each code path.
+* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path.
 * `initialSegment` (`CodePathSegment`) - The initial segment of this code path.
 * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown.
 * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned.
@@ -43,7 +43,7 @@ Difference from doubly linked list is what there are forking and merging (the ne
 
 `CodePathSegment` has the following properties:
 
-* `id` (`string`) - An unique string. Respective rules can use `id` to save additional information for each segment.
+* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment.
 * `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing.
 * `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing.
 * `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`.
diff --git a/docs/developer-guide/development-environment.md b/docs/developer-guide/development-environment.md
index c0bd8e40b..538e297a3 100644
--- a/docs/developer-guide/development-environment.md
+++ b/docs/developer-guide/development-environment.md
@@ -47,7 +47,7 @@ Please see the [generator documentation](https://github.com/eslint/generator-esl
 
 ## Step 5: Run the tests
 
-Running the tests is the best way to ensure you have correctly set up your development environment. Make sure you're in the the `eslint` directory and run:
+Running the tests is the best way to ensure you have correctly set up your development environment. Make sure you're in the `eslint` directory and run:
 
 ```
 npm test
diff --git a/docs/developer-guide/working-with-custom-formatters.md b/docs/developer-guide/working-with-custom-formatters.md
index 6e4d7911a..0a358d513 100644
--- a/docs/developer-guide/working-with-custom-formatters.md
+++ b/docs/developer-guide/working-with-custom-formatters.md
@@ -79,10 +79,10 @@ The following are the fields of the result object:
 - **ruleId**: the id of the rule that produced the error or warning.
 - **severity**: the severity of the failure, `1` for warnings and `2` for errors.
 - **message**: the human readable description of the error.
-- **line**: the line where where the issue is located.
-- **column**: the colum where the issue is located.
+- **line**: the line where the issue is located.
+- **column**: the column where the issue is located.
 - **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/spec.md#node-objects)
-- **source**: a extract of the code the line where the failure happened.
+- **source**: an extract of the code the line where the failure happened.
 
 **Please note**: the `source` property will be removed from the message object in an upcoming breaking release. If you depend on this property, you should now use the `source` or `output` properties from [the result object](#the-result-object) instead.
 
@@ -271,7 +271,7 @@ And then the formatter can read from stdin
 eslint -f './json.js' | ./my-awesome-formatter-cli.js --skip-warnings
 ```
 
-And the the content of **my-awesome-formatter-cli.js** would be something like:
+And the content of **my-awesome-formatter-cli.js** would be something like:
 
 ```javascript
 #!/usr/bin/env node
diff --git a/docs/maintainer-guide/README.md b/docs/maintainer-guide/README.md
index 43813cf5c..845a5bade 100644
--- a/docs/maintainer-guide/README.md
+++ b/docs/maintainer-guide/README.md
@@ -1,6 +1,6 @@
 # Maintainer Guide
 
-This guide is intended for those who work as partof the ESLint project team.
+This guide is intended for those who work as part of the ESLint project team.
 
 ## [Managing Issues](issues.html)
 
diff --git a/docs/rules/arrow-parens.md b/docs/rules/arrow-parens.md
index 2e006c283..371a500ff 100644
--- a/docs/rules/arrow-parens.md
+++ b/docs/rules/arrow-parens.md
@@ -71,7 +71,7 @@ a => a;
 a => {'\n'};
 a.then(foo => {});
 a.then(foo => a);
-a(foo => { if (true) {}; });
+a(foo => { if (true) {} });
 ```
 
 Examples of **correct** code for this rule with the default `"always"` option:
@@ -85,7 +85,7 @@ Examples of **correct** code for this rule with the default `"always"` option:
 (a) => a;
 (a) => {'\n'}
 a.then((foo) => {});
-a.then((foo) => { if (true) {}; });
+a.then((foo) => { if (true) {} });
 ```
 
 #### If Statements
@@ -102,7 +102,7 @@ if (a => b) {
  console.log('bigger');
 } else {
  console.log('smaller');
-};
+}
 // outputs 'bigger', not smaller as expected
 ```
 
@@ -120,7 +120,7 @@ if ((a) => b) {
  console.log('truthy value returned');
 } else {
  console.log('falsey value returned');
-};
+}
 // outputs 'truthy value returned'
 ```
 
@@ -158,7 +158,7 @@ Examples of **incorrect** code for this rule with the `"as-needed"` option:
 (a) => {'\n'};
 a.then((foo) => {});
 a.then((foo) => a);
-a((foo) => { if (true) {}; });
+a((foo) => { if (true) {} });
 ```
 
 Examples of **correct** code for this rule with the `"as-needed"` option:
@@ -172,7 +172,7 @@ a => {};
 a => a;
 a => {'\n'};
 a.then(foo => {});
-a.then(foo => { if (true) {}; });
+a.then(foo => { if (true) {} });
 (a, b, c) => a;
 (a = 10) => a;
 ([a, b]) => a;
@@ -209,8 +209,8 @@ a => ({});
 () => {};
 a => a;
 a.then((foo) => {});
-a.then((foo) => { if (true) {}; });
-a((foo) => { if (true) {}; });
+a.then((foo) => { if (true) {} });
+a((foo) => { if (true) {} });
 (a, b, c) => a;
 (a = 10) => a;
 ([a, b]) => a;
diff --git a/docs/rules/callback-return.md b/docs/rules/callback-return.md
index d9742757e..46b24547b 100644
--- a/docs/rules/callback-return.md
+++ b/docs/rules/callback-return.md
@@ -76,7 +76,7 @@ function bar(err, send) {
 }
 ```
 
-Examples of **correct** code for this rule with the the option `["done", "send.error", "send.success"]`:
+Examples of **correct** code for this rule with the option `["done", "send.error", "send.success"]`:
 
 ```js
 /*eslint callback-return: ["error", ["done", "send.error", "send.success"]]*/
diff --git a/docs/rules/id-length.md b/docs/rules/id-length.md
index 77a1195ca..b63a88b08 100644
--- a/docs/rules/id-length.md
+++ b/docs/rules/id-length.md
@@ -67,7 +67,7 @@ var data = { "x": 1 };  // excused because of quotes
 data["y"] = 3;  // excused because of calculated property access
 ```
 
-This rule has an shorthand integer option for the `"min"` object property.
+This rule has a shorthand integer option for the `"min"` object property.
 
 Examples of **incorrect** code for this rule with a minimum of 4:
 
diff --git a/docs/rules/key-spacing.md b/docs/rules/key-spacing.md
index 48085baa9..2c4afae8c 100644
--- a/docs/rules/key-spacing.md
+++ b/docs/rules/key-spacing.md
@@ -234,7 +234,7 @@ var obj = {
 
 The `multiLine` and `align` options can differ, which allows for fine-tuned control over the `key-spacing` of your files.  `align` will **not** inherit from `multiLine` if `align` is configured as an object.
 
-`multiLine` is used any time  an object literal spans multiple lines.  The `align` configuration is used when there is a group of properties in the the same object. For example:
+`multiLine` is used any time  an object literal spans multiple lines.  The `align` configuration is used when there is a group of properties in the same object. For example:
 
 ```javascript
 var myObj = {
diff --git a/docs/rules/no-self-compare.md b/docs/rules/no-self-compare.md
index 6cf4390cd..a745de404 100644
--- a/docs/rules/no-self-compare.md
+++ b/docs/rules/no-self-compare.md
@@ -1,6 +1,6 @@
 # Disallow Self Compare (no-self-compare)
 
-Comparing a variable against itself is usually an error, either an typo or refactoring error. It is confusing to the reader and may potentially introduce a runtime error.
+Comparing a variable against itself is usually an error, either a typo or refactoring error. It is confusing to the reader and may potentially introduce a runtime error.
 
 The only time you would compare a variable against itself is when you are testing for `NaN`. However, it is far more appropriate to use `typeof x === 'number' && isNaN(x)` or the [Number.isNaN ES2015 function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) for that use case rather than leaving the reader of the code to determine the intent of self comparison.
 
diff --git a/docs/rules/no-tabs.md b/docs/rules/no-tabs.md
index fd738041b..57921aa31 100644
--- a/docs/rules/no-tabs.md
+++ b/docs/rules/no-tabs.md
@@ -12,7 +12,7 @@ Examples of **incorrect** code for this rule:
 var a /t= 2;
 
 /**
-* /t/t its a test function
+* /t/t it's a test function
 */
 function test(){}
 
@@ -25,7 +25,7 @@ Examples of **correct** code for this rule:
 var a = 2;
 
 /**
-* its a test function
+* it's a test function
 */
 function test(){}
 
diff --git a/docs/rules/no-unsafe-finally.md b/docs/rules/no-unsafe-finally.md
index 2ce8f0e4e..1783e501f 100644
--- a/docs/rules/no-unsafe-finally.md
+++ b/docs/rules/no-unsafe-finally.md
@@ -36,7 +36,7 @@ JavaScript suspends the control flow statements of `try` and `catch` blocks unti
     try {
         throw new Error("Try")
     } catch(err) {
-        throw err; // The error thrown from try block is catched and rethrown
+        throw err; // The error thrown from try block is caught and rethrown
     } finally {
         throw new Error("Finally"); // Finally(...) is thrown, which we did not expect
     }
diff --git a/docs/rules/no-use-before-define.md b/docs/rules/no-use-before-define.md
index 99c3123ce..4964d8d09 100644
--- a/docs/rules/no-use-before-define.md
+++ b/docs/rules/no-use-before-define.md
@@ -83,7 +83,7 @@ function g() {
   Otherwise, the rule ignores a reference if the declaration is in an upper scope, while still reporting the reference if it's in the same scope as the declaration.
   Default is `true`.
 
-This rule accepts `"nofunc"` string as a option.
+This rule accepts `"nofunc"` string as an option.
 `"nofunc"` is the same as `{ "functions": false, "classes": true }`.
 
 ### functions
diff --git a/docs/rules/no-var.md b/docs/rules/no-var.md
index 056b733a3..83950c32b 100644
--- a/docs/rules/no-var.md
+++ b/docs/rules/no-var.md
@@ -9,7 +9,7 @@ var count = people.length;
 var enoughFood = count > sandwiches.length;
 
 if (enoughFood) {
-    var count = sandwiches.length; // accidently overriding the count variable
+    var count = sandwiches.length; // accidentally overriding the count variable
     console.log("We have " + count + " sandwiches for everyone. Plenty for all!");
 }
 
diff --git a/docs/rules/no-warning-comments.md b/docs/rules/no-warning-comments.md
index 6b027586f..48365601b 100644
--- a/docs/rules/no-warning-comments.md
+++ b/docs/rules/no-warning-comments.md
@@ -81,5 +81,5 @@ Examples of **correct** code for the `{ "terms": ["todo", "fixme", "any other te
 
 ## When Not To Use It
 
-* If you have a large code base that was not developed with a policy to not use such warning terms, you might get hundreds of warnings / errors which might be contra-productive if you can't fix all of them (e.g. if you don't get the time to do it) as you might overlook other warnings / errors or get used to many of them and don't pay attention on it anymore.
+* If you have a large code base that was not developed with a policy to not use such warning terms, you might get hundreds of warnings / errors which might be counter-productive if you can't fix all of them (e.g. if you don't get the time to do it) as you might overlook other warnings / errors or get used to many of them and don't pay attention on it anymore.
 * Same reason as the point above: You shouldn't configure terms that are used very often (e.g. central parts of the native language used in your comments).
diff --git a/docs/rules/operator-linebreak.md b/docs/rules/operator-linebreak.md
index dab8dbb4b..3b4b2f19d 100644
--- a/docs/rules/operator-linebreak.md
+++ b/docs/rules/operator-linebreak.md
@@ -1,6 +1,6 @@
 # enforce consistent linebreak style for operators (operator-linebreak)
 
-When a statement is too long to fit on a single line, line breaks are generally inserted next to the operators separating expressions. The first style coming to mind would be to place the operator at the end of the line, following the english punctuation rules.
+When a statement is too long to fit on a single line, line breaks are generally inserted next to the operators separating expressions. The first style coming to mind would be to place the operator at the end of the line, following the English punctuation rules.
 
 ```js
 var fullHeight = borderTop +
diff --git a/docs/rules/prefer-const.md b/docs/rules/prefer-const.md
index e4dc2bff5..cf86f5b7b 100644
--- a/docs/rules/prefer-const.md
+++ b/docs/rules/prefer-const.md
@@ -172,7 +172,7 @@ function initialize() {
 timer = setInterval(initialize, 100);
 ```
 
-Examples of **correct** code for the defaut `{"ignoreReadBeforeAssign": false}` option:
+Examples of **correct** code for the default `{"ignoreReadBeforeAssign": false}` option:
 
 ```js
 /*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": false}]*/
diff --git a/docs/rules/require-jsdoc.md b/docs/rules/require-jsdoc.md
index 4f839dc92..8fa31d87d 100644
--- a/docs/rules/require-jsdoc.md
+++ b/docs/rules/require-jsdoc.md
@@ -128,7 +128,7 @@ class Test{
     getDate(){}
 }
 
-setTimeout(() => {}, 10); // since its an anonymous arrow function
+setTimeout(() => {}, 10); // since it's an anonymous arrow function
 ```
 
 ## When Not To Use It
diff --git a/docs/rules/space-before-function-parentheses.md b/docs/rules/space-before-function-parentheses.md
index 96b34bdd1..3856c2235 100644
--- a/docs/rules/space-before-function-parentheses.md
+++ b/docs/rules/space-before-function-parentheses.md
@@ -24,7 +24,7 @@ Style guides may require a space after the `function` keyword for anonymous func
 
 This rule aims to enforce consistent spacing before function parentheses and as such, will warn whenever whitespace doesn't match the preferences specified.
 
-This rule takes one argument. If it is `"always"`, which is the default option, all named functions and anonymous functions must have space before function parentheses. If `"never"` then all named functions and anonymous functions must not have space before function parentheses. If you want different spacing for named and anonymous functions you can pass an configuration object as the rule argument to configure those separately (e. g. `{"anonymous": "always", "named": "never"}`).
+This rule takes one argument. If it is `"always"`, which is the default option, all named functions and anonymous functions must have space before function parentheses. If `"never"` then all named functions and anonymous functions must not have space before function parentheses. If you want different spacing for named and anonymous functions you can pass a configuration object as the rule argument to configure those separately (e. g. `{"anonymous": "always", "named": "never"}`).
 
 Examples of **incorrect** code for this rule with the default `"always"` option:
 
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 98214c3f5..8e8b6206e 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -339,7 +339,7 @@ This saves the output into the `results.txt` file.
 
 #### `--color`, `--no-color`
 
-This option forces the enabling/disabling of colorized output. You can use this to override the default behavior, which is to enable colorized output unless no TTY is detected, such as when when piping `eslint` through `cat` or `less`.
+This option forces the enabling/disabling of colorized output. You can use this to override the default behavior, which is to enable colorized output unless no TTY is detected, such as when piping `eslint` through `cat` or `less`.
 
 Examples:
 
diff --git a/lib/rules/func-name-matching.js b/lib/rules/func-name-matching.js
index fc6fa8588..db06d5d46 100644
--- a/lib/rules/func-name-matching.js
+++ b/lib/rules/func-name-matching.js
@@ -132,6 +132,15 @@ module.exports = {
             });
         }
 
+        /**
+         * Determines whether a given node is a string literal
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} `true` if the node is a string literal
+         */
+        function isStringLiteral(node) {
+            return node.type === "Literal" && typeof node.value === "string";
+        }
+
         //--------------------------------------------------------------------------
         // Public
         //--------------------------------------------------------------------------
@@ -139,7 +148,7 @@ module.exports = {
         return {
 
             VariableDeclarator(node) {
-                if (!node.init || node.init.type !== "FunctionExpression") {
+                if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
                     return;
                 }
                 if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
@@ -148,10 +157,12 @@ module.exports = {
             },
 
             AssignmentExpression(node) {
-                if (node.right.type !== "FunctionExpression" ||
-                   (node.left.computed && node.left.property.type !== "Literal") ||
-                    (!includeModuleExports && isModuleExports(node.left))
-                   ) {
+                if (
+                    node.right.type !== "FunctionExpression" ||
+                    (node.left.computed && node.left.property.type !== "Literal") ||
+                    (!includeModuleExports && isModuleExports(node.left)) ||
+                    (node.left.type !== "Identifier" && node.left.type !== "MemberExpression")
+                ) {
                     return;
                 }
 
@@ -164,13 +175,13 @@ module.exports = {
             },
 
             Property(node) {
-                if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key.type !== "Literal") {
+                if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
                     return;
                 }
                 if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) {
                     report(node, node.key.name, node.value.id.name, true);
                 } else if (
-                    node.key.type === "Literal" &&
+                    isStringLiteral(node.key) &&
                     isIdentifier(node.key.value, ecmaVersion) &&
                     shouldWarn(node.key.value, node.value.id.name)
                 ) {
diff --git a/lib/rules/sort-vars.js b/lib/rules/sort-vars.js
index 6bee22103..1fdfc9ace 100644
--- a/lib/rules/sort-vars.js
+++ b/lib/rules/sort-vars.js
@@ -37,11 +37,9 @@ module.exports = {
 
         return {
             VariableDeclaration(node) {
-                node.declarations.reduce((memo, decl) => {
-                    if (decl.id.type === "ObjectPattern" || decl.id.type === "ArrayPattern") {
-                        return memo;
-                    }
+                const idDeclarations = node.declarations.filter(decl => decl.id.type === "Identifier");
 
+                idDeclarations.slice(1).reduce((memo, decl) => {
                     let lastVariableName = memo.id.name,
                         currenVariableName = decl.id.name;
 
@@ -56,7 +54,7 @@ module.exports = {
                     }
                     return decl;
 
-                }, node.declarations[0]);
+                }, idDeclarations[0]);
             }
         };
     }
diff --git a/lib/util/traverser.js b/lib/util/traverser.js
index d5710bb8a..fc070186b 100644
--- a/lib/util/traverser.js
+++ b/lib/util/traverser.js
@@ -14,41 +14,32 @@ const estraverse = require("estraverse");
 // Helpers
 //------------------------------------------------------------------------------
 
-const KEY_BLACKLIST = [
+const KEY_BLACKLIST = new Set([
     "parent",
     "leadingComments",
     "trailingComments"
-];
+]);
 
 /**
  * Wrapper around an estraverse controller that ensures the correct keys
  * are visited.
  * @constructor
  */
-function Traverser() {
-
-    const controller = Object.create(new estraverse.Controller()),
-        originalTraverse = controller.traverse;
-
-    // intercept call to traverse() and add the fallback key to the visitor
-    controller.traverse = function(node, visitor) {
+class Traverser extends estraverse.Controller {
+    traverse(node, visitor) {
         visitor.fallback = Traverser.getKeys;
-        return originalTraverse.call(this, node, visitor);
-    };
-
-    return controller;
+        return super.traverse(node, visitor);
+    }
+
+    /**
+     * Calculates the keys to use for traversal.
+     * @param {ASTNode} node The node to read keys from.
+     * @returns {string[]} An array of keys to visit on the node.
+     * @private
+     */
+    static getKeys(node) {
+        return Object.keys(node).filter(key => !KEY_BLACKLIST.has(key));
+    }
 }
 
-/**
- * Calculates the keys to use for traversal.
- * @param {ASTNode} node The node to read keys from.
- * @returns {string[]} An array of keys to visit on the node.
- * @private
- */
-Traverser.getKeys = function(node) {
-    return Object.keys(node).filter(key => KEY_BLACKLIST.indexOf(key) === -1);
-};
-
 module.exports = Traverser;
-
-
diff --git a/package.json b/package.json
index d9a0110b6..4fd3423cc 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
   "dependencies": {
     "babel-code-frame": "^6.16.0",
     "chalk": "^1.1.3",
-    "concat-stream": "^1.4.6",
+    "concat-stream": "^1.5.2",
     "debug": "^2.1.1",
     "doctrine": "^1.2.2",
     "escope": "^3.6.0",
@@ -97,7 +97,7 @@
     "load-perf": "^0.2.0",
     "markdownlint": "^0.3.1",
     "mocha": "^2.4.5",
-    "mock-fs": "not-an-aardvark/mock-fs#06868bbd7724707f9324b237bdde28f05f7a01d5",
+    "mock-fs": "^4.2.0",
     "npm-license": "^0.3.2",
     "phantomjs-prebuilt": "^2.1.7",
     "proxyquire": "^1.7.10",


Test patch:
diff --git a/tests/lib/rules/func-name-matching.js b/tests/lib/rules/func-name-matching.js
index 5d98cd388..8f0d81425 100644
--- a/tests/lib/rules/func-name-matching.js
+++ b/tests/lib/rules/func-name-matching.js
@@ -117,6 +117,66 @@ ruleTester.run("func-name-matching", rule, {
         {
             code: "({[foo]: function bar() {}})",
             parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[null]: function foo() {}})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[1]: function foo() {}})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[true]: function foo() {}})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[`x`]: function foo() {}})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[/abc/]: function foo() {}})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[[1, 2, 3]]: function foo() {}})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[{x: 1}]: function foo() {}})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "[] = function foo() {}",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({} = function foo() {})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "[a] = function foo() {}",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({a} = function foo() {})",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var [] = function foo() {}",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var {} = function foo() {}",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var [a] = function foo() {}",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var {a} = function foo() {}",
+            parserOptions: { ecmaVersion: 6 }
         }
     ],
     invalid: [
diff --git a/tests/lib/rules/sort-vars.js b/tests/lib/rules/sort-vars.js
index 3994f52cd..4411c35ef 100644
--- a/tests/lib/rules/sort-vars.js
+++ b/tests/lib/rules/sort-vars.js
@@ -77,7 +77,13 @@ ruleTester.run("sort-vars", rule, {
             "            c;",
             "    }",
             "}"
-        ].join("\n"), env: { es6: true }, parserOptions: { sourceType: "module" } }
+        ].join("\n"), env: { es6: true }, parserOptions: { sourceType: "module" } },
+
+        {
+            code: "var {} = 1, a",
+            options: ignoreCaseArgs,
+            parserOptions: { ecmaVersion: 6 }
+        }
     ],
     invalid: [
         { code: "var b, a", errors: [expectedError] },
@@ -93,6 +99,13 @@ ruleTester.run("sort-vars", rule, {
         { code: "var d, a, [b, c] = {};", options: ignoreCaseArgs,
             parserOptions: { ecmaVersion: 6 }, errors: [expectedError] },
         { code: "var d, a, [b, {x: {c, e}}] = {};", options: ignoreCaseArgs,
-            parserOptions: { ecmaVersion: 6 }, errors: [expectedError] }
+            parserOptions: { ecmaVersion: 6 }, errors: [expectedError] },
+
+        {
+            code: "var {} = 1, b, a",
+            options: ignoreCaseArgs,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [expectedError]
+        }
     ]
 });
diff --git a/tests/lib/util/traverser.js b/tests/lib/util/traverser.js
new file mode 100644
index 000000000..68be3977a
--- /dev/null
+++ b/tests/lib/util/traverser.js
@@ -0,0 +1,43 @@
+"use strict";
+
+const assert = require("chai").assert;
+const Traverser = require("../../../lib/util/traverser");
+
+describe("Traverser", () => {
+    it("traverses all keys except 'parent', 'leadingComments', and 'trailingComments'", () => {
+        const traverser = new Traverser();
+        const fakeAst = {
+            type: "Program",
+            body: [
+                {
+                    type: "ExpressionStatement",
+                    leadingComments: {
+                        type: "Line"
+                    },
+                    trailingComments: {
+                        type: "Block"
+                    }
+                },
+                {
+                    type: "FooStatement",
+                    foo: {
+                        type: "BarStatement"
+                    }
+                }
+            ]
+        };
+
+        fakeAst.body[0].parent = fakeAst;
+
+        const enteredNodes = [];
+        const exitedNodes = [];
+
+        traverser.traverse(fakeAst, {
+            enter: node => enteredNodes.push(node),
+            leave: node => exitedNodes.push(node)
+        });
+
+        assert.deepEqual(enteredNodes, [fakeAst, fakeAst.body[0], fakeAst.body[1], fakeAst.body[1].foo]);
+        assert.deepEqual(exitedNodes, [fakeAst.body[0], fakeAst.body[1].foo, fakeAst.body[1], fakeAst]);
+    });
+});