Repository: eslint/eslint
Pull request #10211 (2018-04-12 22:03:18)

Chore: enable rest/spread rules on ESLint codebase
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

This enables the `prefer-rest-params`, `prefer-spread`, and `rest-spread-spacing` rules on the ESLint codebase.

This is blocked on https://github.com/eslint/eslint/pull/10074 (and the tests will fail as a result), but the tests should pass after https://github.com/eslint/eslint/pull/10074 is merged.

This should avoid the concerns expressed in https://github.com/eslint/eslint/issues/10052#issuecomment-377384575 that ESLint v5 might initially keep working with Node 4 even though we officially drop support.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular



Patch:
diff --git a/.travis.yml b/.travis.yml
index d481188d3..5763f04af 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,6 @@
 language: node_js
 node_js:
-    - "4"
-    - "5"
     - "6"
-    - "7"
     - "8"
     - "9"
 sudo: false
diff --git a/Makefile.js b/Makefile.js
index 63b6204fa..78063832f 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -979,10 +979,7 @@ function createConfigForPerformanceTest() {
         "rules:"
     ];
 
-    content.push.apply(
-        content,
-        ls("lib/rules").map(fileName => `    ${path.basename(fileName, ".js")}: 1`)
-    );
+    content.push(...ls("lib/rules").map(fileName => `    ${path.basename(fileName, ".js")}: 1`));
 
     content.join("\n").to(PERF_ESLINTRC);
 }
diff --git a/README.md b/README.md
index ccf0cda62..ed991e14f 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 
 ## Installation and Usage
 
-Prerequisites: [Node.js](https://nodejs.org/en/) (>=4.x), npm version 2+.
+Prerequisites: [Node.js](https://nodejs.org/en/) (>=6.14), npm version 3+.
 
 There are two ways to install ESLint: globally and locally.
 
diff --git a/docs/rules/no-unused-vars.md b/docs/rules/no-unused-vars.md
index d7502ed5c..996dea181 100644
--- a/docs/rules/no-unused-vars.md
+++ b/docs/rules/no-unused-vars.md
@@ -150,7 +150,7 @@ console.log(secondVar);
 
 The `args` option has three settings:
 
-* `after-used` - only the last argument must be used. This allows you, for instance, to have two named parameters to a function and as long as you use the second argument, ESLint will not warn you about the first. This is the default setting.
+* `after-used` - unused positional arguments that occur before the last used argument will not be checked, but all named arguments and all positional arguments after the last used argument will be checked.
 * `all` - all named arguments must be used.
 * `none` - do not check arguments.
 
@@ -161,9 +161,10 @@ Examples of **incorrect** code for the default `{ "args": "after-used" }` option
 ```js
 /*eslint no-unused-vars: ["error", { "args": "after-used" }]*/
 
-// 1 error
+// 2 errors, for the parameters after the last used parameter (bar)
 // "baz" is defined but never used
-(function(foo, bar, baz) {
+// "qux" is defined but never used
+(function(foo, bar, baz, qux) {
     return bar;
 })();
 ```
@@ -173,8 +174,8 @@ Examples of **correct** code for the default `{ "args": "after-used" }` option:
 ```js
 /*eslint no-unused-vars: ["error", {"args": "after-used"}]*/
 
-(function(foo, bar, baz) {
-    return baz;
+(function(foo, bar, baz, qux) {
+    return qux;
 })();
 ```
 
diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index 22dcd497f..4b98eb075 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -13,7 +13,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 
 ## Installation and Usage
 
-Prerequisites: [Node.js](https://nodejs.org/en/) (>=4.x), npm version 2+.
+Prerequisites: [Node.js](https://nodejs.org/en/) (>=6.14), npm version 3+.
 
 There are two ways to install ESLint: globally and locally.
 
diff --git a/lib/file-finder.js b/lib/file-finder.js
index 11091a99d..5d4b48a62 100644
--- a/lib/file-finder.js
+++ b/lib/file-finder.js
@@ -134,7 +134,7 @@ class FileFinder {
 
         // Add what has been cached previously to the cache of each directory searched.
         for (let i = 0; i < searched; i++) {
-            [].push.apply(cache[dirs[i]], cache[directory]);
+            cache[dirs[i]].push(...cache[directory]);
         }
 
         yield* cache[dirs[0]];
diff --git a/lib/linter.js b/lib/linter.js
index 2d46b4bd8..695e701c1 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -284,7 +284,7 @@ function getDirectiveComments(filename, ast, ruleMapper) {
         if (/^eslint-disable-(next-)?line$/.test(match[1]) && comment.loc.start.line === comment.loc.end.line) {
             const directiveType = match[1].slice("eslint-".length);
 
-            [].push.apply(disableDirectives, createDisableDirectives(directiveType, comment.loc.start, directiveValue));
+            disableDirectives.push(...createDisableDirectives(directiveType, comment.loc.start, directiveValue));
         } else if (comment.type === "Block") {
             switch (match[1]) {
                 case "exported":
@@ -297,11 +297,11 @@ function getDirectiveComments(filename, ast, ruleMapper) {
                     break;
 
                 case "eslint-disable":
-                    [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, directiveValue));
+                    disableDirectives.push(...createDisableDirectives("disable", comment.loc.start, directiveValue));
                     break;
 
                 case "eslint-enable":
-                    [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, directiveValue));
+                    disableDirectives.push(...createDisableDirectives("enable", comment.loc.start, directiveValue));
                     break;
 
                 case "eslint": {
@@ -724,10 +724,8 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
     Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
         (contextInfo, methodName) =>
             Object.assign(contextInfo, {
-                [methodName]() {
-                    const sourceCode = this.getSourceCode();
-
-                    return sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]].apply(sourceCode, arguments);
+                [methodName](...args) {
+                    return this.getSourceCode()[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args);
                 }
             }),
         {}
@@ -816,7 +814,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
                 {
                     id: ruleId,
                     options: getRuleOptions(configuredRules[ruleId]),
-                    report() {
+                    report(...args) {
 
                         /*
                          * Create a report translator lazily.
@@ -831,7 +829,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
                         if (reportTranslator === null) {
                             reportTranslator = createReportTranslator({ ruleId, severity, sourceCode, messageIds });
                         }
-                        const problem = reportTranslator.apply(null, arguments);
+                        const problem = reportTranslator(...args);
 
                         if (problem.fix && rule.meta && !rule.meta.fixable) {
                             throw new Error("Fixable rules should export a `meta.fixable` property.");
diff --git a/lib/logging.js b/lib/logging.js
index 22451e535..3eb898c34 100644
--- a/lib/logging.js
+++ b/lib/logging.js
@@ -14,15 +14,15 @@ module.exports = {
      * Cover for console.log
      * @returns {void}
      */
-    info() {
-        console.log.apply(console, arguments);
+    info(...args) {
+        console.log(...args);
     },
 
     /**
      * Cover for console.error
      * @returns {void}
      */
-    error() {
-        console.error.apply(console, arguments);
+    error(...args) {
+        console.error(...args);
     }
 };
diff --git a/lib/report-translator.js b/lib/report-translator.js
index 17d410d2a..f82830586 100644
--- a/lib/report-translator.js
+++ b/lib/report-translator.js
@@ -51,35 +51,35 @@ const interpolate = require("./util/interpolate");
 
 /**
  * Translates a multi-argument context.report() call into a single object argument call
- * @param {...*} arguments A list of arguments passed to `context.report`
+ * @param {...*} args A list of arguments passed to `context.report`
  * @returns {MessageDescriptor} A normalized object containing report information
  */
-function normalizeMultiArgReportCall() {
+function normalizeMultiArgReportCall(...args) {
 
     // If there is one argument, it is considered to be a new-style call already.
-    if (arguments.length === 1) {
+    if (args.length === 1) {
 
         // Shallow clone the object to avoid surprises if reusing the descriptor
-        return Object.assign({}, arguments[0]);
+        return Object.assign({}, args[0]);
     }
 
     // If the second argument is a string, the arguments are interpreted as [node, message, data, fix].
-    if (typeof arguments[1] === "string") {
+    if (typeof args[1] === "string") {
         return {
-            node: arguments[0],
-            message: arguments[1],
-            data: arguments[2],
-            fix: arguments[3]
+            node: args[0],
+            message: args[1],
+            data: args[2],
+            fix: args[3]
         };
     }
 
     // Otherwise, the arguments are interpreted as [node, loc, message, data, fix].
     return {
-        node: arguments[0],
-        loc: arguments[1],
-        message: arguments[2],
-        data: arguments[3],
-        fix: arguments[4]
+        node: args[0],
+        loc: args[1],
+        message: args[2],
+        data: args[3],
+        fix: args[4]
     };
 }
 
@@ -240,8 +240,8 @@ module.exports = function createReportTranslator(metadata) {
      * called every time a rule reports a problem, which happens much less frequently (usually, the vast
      * majority of rules don't report any problems for a given file).
      */
-    return function() {
-        const descriptor = normalizeMultiArgReportCall.apply(null, arguments);
+    return (...args) => {
+        const descriptor = normalizeMultiArgReportCall(...args);
 
         assertValidNodeInfo(descriptor);
 
diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js
index f1840aaf2..7f9adc10a 100644
--- a/lib/rules/no-irregular-whitespace.js
+++ b/lib/rules/no-irregular-whitespace.js
@@ -225,7 +225,7 @@ module.exports = {
 
                 // If we have any errors remaining report on them
                 errors.forEach(error => {
-                    context.report.apply(context, error);
+                    context.report(...error);
                 });
             };
         } else {
diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js
index d01231ff9..17ca1291d 100644
--- a/lib/rules/no-shadow.js
+++ b/lib/rules/no-shadow.js
@@ -179,7 +179,7 @@ module.exports = {
                 while (stack.length) {
                     const scope = stack.pop();
 
-                    stack.push.apply(stack, scope.childScopes);
+                    stack.push(...scope.childScopes);
                     checkForShadows(scope);
                 }
             }
diff --git a/lib/rules/no-undefined.js b/lib/rules/no-undefined.js
index 94b514e91..8491ab5d5 100644
--- a/lib/rules/no-undefined.js
+++ b/lib/rules/no-undefined.js
@@ -68,7 +68,7 @@ module.exports = {
                 while (stack.length) {
                     const scope = stack.pop();
 
-                    stack.push.apply(stack, scope.childScopes);
+                    stack.push(...scope.childScopes);
                     checkScope(scope);
                 }
             }
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index 6ba573498..0d5194bc8 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -457,30 +457,19 @@ module.exports = {
         }
 
         /**
-         * Checks whether the given variable is the last parameter in the non-ignored parameters.
+         * Checks whether the given variable is after the last used parameter.
          *
          * @param {eslint-scope.Variable} variable - The variable to check.
-         * @returns {boolean} `true` if the variable is the last.
+         * @returns {boolean} `true` if the variable is defined after the last
+         * used parameter.
          */
-        function isLastInNonIgnoredParameters(variable) {
+        function isAfterLastUsedArg(variable) {
             const def = variable.defs[0];
+            const params = context.getDeclaredVariables(def.node);
+            const posteriorParams = params.slice(params.indexOf(variable) + 1);
 
-            // This is the last.
-            if (def.index === def.node.params.length - 1) {
-                return true;
-            }
-
-            // if all parameters preceded by this variable are ignored and unused, this is the last.
-            if (config.argsIgnorePattern) {
-                const params = context.getDeclaredVariables(def.node);
-                const posteriorParams = params.slice(params.indexOf(variable) + 1);
-
-                if (posteriorParams.every(v => v.references.length === 0 && config.argsIgnorePattern.test(v.name))) {
-                    return true;
-                }
-            }
-
-            return false;
+            // If any used parameters occur after this parameter, do not report.
+            return !posteriorParams.some(v => v.references.length > 0);
         }
 
         /**
@@ -549,8 +538,8 @@ module.exports = {
                                 continue;
                             }
 
-                            // if "args" option is "after-used", skip all but the last parameter
-                            if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isLastInNonIgnoredParameters(variable)) {
+                            // if "args" option is "after-used", skip used variables
+                            if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) {
                                 continue;
                             }
                         } else {
diff --git a/lib/timing.js b/lib/timing.js
index 9452e419b..102a5233d 100644
--- a/lib/timing.js
+++ b/lib/timing.js
@@ -116,10 +116,10 @@ module.exports = (function() {
             data[key] = 0;
         }
 
-        return function() {
+        return function(...args) {
             let t = process.hrtime();
 
-            fn.apply(null, Array.prototype.slice.call(arguments));
+            fn(...args);
             t = process.hrtime(t);
             data[key] += t[0] * 1e3 + t[1] / 1e6;
         };
diff --git a/lib/util/ajv.js b/lib/util/ajv.js
index f9e8b9853..285176da8 100644
--- a/lib/util/ajv.js
+++ b/lib/util/ajv.js
@@ -19,7 +19,8 @@ const ajv = new Ajv({
     meta: false,
     validateSchema: false,
     missingRefs: "ignore",
-    verbose: true
+    verbose: true,
+    schemaId: "auto"
 });
 
 ajv.addMetaSchema(metaSchema);
diff --git a/lib/util/safe-emitter.js b/lib/util/safe-emitter.js
index 2fa373cb9..ab212230d 100644
--- a/lib/util/safe-emitter.js
+++ b/lib/util/safe-emitter.js
@@ -27,8 +27,6 @@
  * another module throws an error or registers a listener.
  * 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a
  * `this` value of the emitter instance, which would give listeners access to other listeners.)
- * 3. Events can be emitted with at most 3 arguments. (For example: when using `emitter.emit('foo', a, b, c)`,
- * the arguments `a`, `b`, and `c` will be passed to the listener functions.)
  * @returns {SafeEmitter} An emitter
  */
 module.exports = () => {
@@ -42,9 +40,9 @@ module.exports = () => {
                 listeners[eventName] = [listener];
             }
         },
-        emit(eventName, a, b, c) {
+        emit(eventName, ...args) {
             if (eventName in listeners) {
-                listeners[eventName].forEach(listener => listener(a, b, c));
+                listeners[eventName].forEach(listener => listener(...args));
             }
         },
         eventNames() {
diff --git a/package.json b/package.json
index 646ec312d..643e047a1 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
   "homepage": "https://eslint.org",
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
-    "ajv": "^5.3.0",
+    "ajv": "^6.0.1",
     "babel-code-frame": "^6.26.0",
     "chalk": "^2.1.0",
     "concat-stream": "^1.6.2",
@@ -70,7 +70,7 @@
     "semver": "^5.5.0",
     "strip-ansi": "^4.0.0",
     "strip-json-comments": "^2.0.1",
-    "table": "4.0.2",
+    "table": "^4.0.3",
     "text-table": "^0.2.0"
   },
   "devDependencies": {
@@ -122,6 +122,6 @@
   ],
   "license": "MIT",
   "engines": {
-    "node": ">=4"
+    "node": "^6.14.0 || ^8.10.0 || >=9.10.0"
   }
 }
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index a2f9ed742..8c5ac071c 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -152,11 +152,14 @@ rules:
     prefer-const: "error"
     prefer-numeric-literals: "error"
     prefer-promise-reject-errors: "error"
+    prefer-rest-params: "error"
+    prefer-spread: "error"
     prefer-template: "error"
     quotes: ["error", "double"]
     quote-props: ["error", "as-needed"]
     radix: "error"
     require-jsdoc: "error"
+    rest-spread-spacing: "error"
     semi: "error"
     semi-spacing: ["error", {before: false, after: true}]
     semi-style: "error"


Test patch:
diff --git a/tests/lib/ast-utils.js b/tests/lib/ast-utils.js
index 538b941cd..c728b8457 100644
--- a/tests/lib/ast-utils.js
+++ b/tests/lib/ast-utils.js
@@ -42,10 +42,10 @@ describe("ast-utils", () => {
      */
     function mustCall(func) {
         callCounts.set(func, 0);
-        return function Wrapper() {
+        return function Wrapper(...args) {
             callCounts.set(func, callCounts.get(func) + 1);
 
-            return func.apply(this, arguments);
+            return func.call(this, ...args);
         };
     }
 
diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js
index 54f018da0..acbdba72d 100644
--- a/tests/lib/cli-engine.js
+++ b/tests/lib/cli-engine.js
@@ -46,15 +46,11 @@ describe("CLIEngine", () => {
      * @returns {string} The path inside the fixture directory.
      * @private
      */
-    function getFixturePath() {
-        const args = Array.prototype.slice.call(arguments);
-
-        args.unshift(fixtureDir);
-        let filepath = path.join.apply(path, args);
+    function getFixturePath(...args) {
+        const filepath = path.join(fixtureDir, ...args);
 
         try {
-            filepath = fs.realpathSync(filepath);
-            return filepath;
+            return fs.realpathSync(filepath);
         } catch (e) {
             return filepath;
         }
@@ -565,11 +561,11 @@ describe("CLIEngine", () => {
             /* eslint-disable no-underscore-dangle */
             before(() => {
                 originalFindPath = Module._findPath;
-                Module._findPath = function(id) {
+                Module._findPath = function(id, ...otherArgs) {
                     if (id === "@scope/eslint-plugin") {
                         return path.resolve(__dirname, "../fixtures/plugin-shorthand/basic/node_modules/@scope/eslint-plugin/index.js");
                     }
-                    return originalFindPath.apply(this, arguments);
+                    return originalFindPath.call(this, id, ...otherArgs);
                 };
             });
             after(() => {
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index 7c8801b72..04a966c81 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -72,11 +72,8 @@ describe("cli", () => {
      * @returns {string} The path inside the fixture directory.
      * @private
      */
-    function getFixturePath() {
-        const args = Array.prototype.slice.call(arguments);
-
-        args.unshift(fixtureDir);
-        return path.join.apply(path, args);
+    function getFixturePath(...args) {
+        return path.join(fixtureDir, ...args);
     }
 
     // copy into clean area so as not to get "infected" by this project's .eslintrc files
diff --git a/tests/lib/config.js b/tests/lib/config.js
index 9b7458db1..1f9690339 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -97,12 +97,8 @@ describe("Config", () => {
      * @returns {string} The path inside the fixture directory.
      * @private
      */
-    function getFixturePath() {
-        const args = Array.prototype.slice.call(arguments);
-
-        args.unshift("config-hierarchy");
-        args.unshift(fixtureDir);
-        return path.join.apply(path, args);
+    function getFixturePath(...args) {
+        return path.join(fixtureDir, "config-hierarchy", ...args);
     }
 
     /**
@@ -184,14 +180,8 @@ describe("Config", () => {
          * @returns {string} The path inside the fixture directory.
          * @private
          */
-        function getFakeFixturePath() {
-            const args = Array.prototype.slice.call(arguments);
-
-            args.unshift("config-hierarchy");
-            args.unshift("fixtures");
-            args.unshift("eslint");
-            args.unshift(process.cwd());
-            return path.join.apply(path, args);
+        function getFakeFixturePath(...args) {
+            return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
         }
 
         before(() => {
@@ -834,14 +824,8 @@ describe("Config", () => {
              * @returns {string} The path inside the fixture directory.
              * @private
              */
-            function getFakeFixturePath() {
-                const args = Array.prototype.slice.call(arguments);
-
-                args.unshift("config-hierarchy");
-                args.unshift("fixtures");
-                args.unshift("eslint");
-                args.unshift(process.cwd());
-                return path.join.apply(path, args);
+            function getFakeFixturePath(...args) {
+                return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
             }
 
             /**
@@ -979,14 +963,8 @@ describe("Config", () => {
              * @returns {string} The path inside the fixture directory.
              * @private
              */
-            function getFakeFixturePath() {
-                const args = Array.prototype.slice.call(arguments);
-
-                args.unshift("config-hierarchy");
-                args.unshift("fixtures");
-                args.unshift("eslint");
-                args.unshift(process.cwd());
-                return path.join.apply(path, args);
+            function getFakeFixturePath(...args) {
+                return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
             }
 
             /**
@@ -1117,15 +1095,8 @@ describe("Config", () => {
              * @returns {string} The path inside the fixture directory.
              * @private
              */
-            function getFakeFixturePath() {
-                const pathSegments = Array.from(arguments);
-
-                pathSegments.unshift("config-hierarchy");
-                pathSegments.unshift("fixtures");
-                pathSegments.unshift("eslint");
-                pathSegments.unshift(process.cwd());
-
-                return path.join.apply(path, pathSegments);
+            function getFakeFixturePath(...pathSegments) {
+                return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...pathSegments);
             }
 
             before(() => {
diff --git a/tests/lib/config/config-initializer.js b/tests/lib/config/config-initializer.js
index a9b12bcb1..ceeed0cb3 100644
--- a/tests/lib/config/config-initializer.js
+++ b/tests/lib/config/config-initializer.js
@@ -64,15 +64,11 @@ describe("configInitializer", () => {
      * @returns {string} The path inside the fixture directory.
      * @private
      */
-    function getFixturePath() {
-        const args = Array.prototype.slice.call(arguments);
-
-        args.unshift(fixtureDir);
-        let filepath = path.join.apply(path, args);
+    function getFixturePath(...args) {
+        const filepath = path.join(fixtureDir, ...args);
 
         try {
-            filepath = fs.realpathSync(filepath);
-            return filepath;
+            return fs.realpathSync(filepath);
         } catch (e) {
             return filepath;
         }
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index d7e077fe1..42fe5e0e3 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -95,11 +95,8 @@ function countDefaultPatterns(ignoredPaths) {
  * @returns {string} The path inside the fixture directory.
  * @private
  */
-function getFixturePath() {
-    const args = Array.prototype.slice.call(arguments);
-
-    args.unshift(fs.realpathSync(fixtureDir));
-    return path.join.apply(path, args);
+function getFixturePath(...args) {
+    return path.join(fs.realpathSync(fixtureDir), ...args);
 }
 
 //------------------------------------------------------------------------------
diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js
index 3f529aa39..f188f8ce3 100644
--- a/tests/lib/rules/no-unused-vars.js
+++ b/tests/lib/rules/no-unused-vars.js
@@ -596,25 +596,59 @@ ruleTester.run("no-unused-vars", rule, {
         {
             code: "(function(a, b, c) {})",
             options: [{ argsIgnorePattern: "c" }],
-            errors: [{ message: "'b' is defined but never used. Allowed unused args must match /c/." }]
+            errors: [
+                {
+                    message: "'a' is defined but never used. Allowed unused args must match /c/."
+                },
+                {
+                    message: "'b' is defined but never used. Allowed unused args must match /c/."
+                }
+            ]
         },
         {
             code: "(function(a, b, {c, d}) {})",
             options: [{ argsIgnorePattern: "[cd]" }],
             parserOptions: { ecmaVersion: 6 },
-            errors: [{ message: "'b' is defined but never used. Allowed unused args must match /[cd]/." }]
+            errors: [
+                {
+                    message: "'a' is defined but never used. Allowed unused args must match /[cd]/."
+                },
+                {
+                    message: "'b' is defined but never used. Allowed unused args must match /[cd]/."
+                }
+            ]
         },
         {
             code: "(function(a, b, {c, d}) {})",
             options: [{ argsIgnorePattern: "c" }],
             parserOptions: { ecmaVersion: 6 },
-            errors: [{ message: "'d' is defined but never used. Allowed unused args must match /c/." }]
+            errors: [
+                {
+                    message: "'a' is defined but never used. Allowed unused args must match /c/."
+                },
+                {
+                    message: "'b' is defined but never used. Allowed unused args must match /c/."
+                },
+                {
+                    message: "'d' is defined but never used. Allowed unused args must match /c/."
+                }
+            ]
         },
         {
             code: "(function(a, b, {c, d}) {})",
             options: [{ argsIgnorePattern: "d" }],
             parserOptions: { ecmaVersion: 6 },
-            errors: [{ message: "'c' is defined but never used. Allowed unused args must match /d/." }]
+            errors: [
+                {
+                    message: "'a' is defined but never used. Allowed unused args must match /d/."
+                },
+                {
+                    message: "'b' is defined but never used. Allowed unused args must match /d/."
+                },
+                {
+                    message: "'c' is defined but never used. Allowed unused args must match /d/."
+                }
+            ]
         },
         {
             code: "/*global\rfoo*/",
diff --git a/tests/lib/util/glob-util.js b/tests/lib/util/glob-util.js
index d4ac651b8..47c601d26 100644
--- a/tests/lib/util/glob-util.js
+++ b/tests/lib/util/glob-util.js
@@ -26,11 +26,8 @@ let fixtureDir;
  * @returns {string} The path inside the fixture directory.
  * @private
  */
-function getFixturePath() {
-    const args = Array.prototype.slice.call(arguments);
-
-    args.unshift(fs.realpathSync(fixtureDir));
-    return path.join.apply(path, args);
+function getFixturePath(...args) {
+    return path.join(fs.realpathSync(fixtureDir), ...args);
 }
 
 //------------------------------------------------------------------------------
diff --git a/tests/lib/util/source-code-util.js b/tests/lib/util/source-code-util.js
index 44bb6613d..374443321 100644
--- a/tests/lib/util/source-code-util.js
+++ b/tests/lib/util/source-code-util.js
@@ -35,11 +35,8 @@ describe("SourceCodeUtil", () => {
      * @returns {string} The path inside the fixture directory.
      * @private
      */
-    function getFixturePath() {
-        const args = Array.prototype.slice.call(arguments);
-
-        args.unshift(fixtureDir);
-        let filepath = path.join.apply(path, args);
+    function getFixturePath(...args) {
+        let filepath = path.join(fixtureDir, ...args);
 
         try {
             filepath = fs.realpathSync(filepath);