Repository: eslint/eslint
Pull request #9782 (2017-12-28 14:41:06)

New: CLIEngine#getRules() (refs #6582)
<!--
    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)**

[x] 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
[x] Add a CLI <del>option</del> <ins>method</ins>
[ ] 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)**

- add CLIEngine#getRules() as discussed in [TSC](https://github.com/eslint/tsc-meetings/blob/3ef527b67712b5cecd0e2231dfb1a8a59a052155/notes/2017/2017-12-07.md#proposal-add-url-for-a-rules-documentation-to-the-api)
- update the documentation and unit tests


**Is there anything you'd like reviewers to focus on?**

Next step::
- add `meta.docs.url` to each of the built-in rules
- document `meta.docs.url` as a property that third party rules are encouraged to include



j-f1: Would it be possible to add a method on the exported plugin object that generates the docs URL so that people can optionally DRY out their code? (i.e. ``getDocsURL: name => `https://github.com/foo/eslint-plugin-bar/blob/master/docs/rules/${name}.md` `` instead of providing `meta.docs.url` on each rule)
platinumazure: That could be interesting/valuable, but this PR probably isn't the place to
discuss :-)

On Dec 28, 2017 11:25 AM, "Jed Fox" <notifications@github.com> wrote:

> Would it be possible to add a method on the exported plugin object that
> generates the docs URL so that people can optionally DRY out their code?
> (i.e. getDocsURL: name => `https://github.com/foo/
> eslint-plugin-bar/blob/master/docs/rules/${name}.md`
> <https://github.com/foo/eslint-plugin-bar/blob/master/docs/rules/$%7Bname%7D.md>
> instead of providing meta.docs.url on each rule)
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/eslint/eslint/pull/9782#issuecomment-354324043>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AARWenHw8utTXlFhW-6gXs_IpfKWUDTKks5tE875gaJpZM4ROYPV>
> .
>

ilyavolodin: Also it already can be done by extracting the logic into a separate file, and importing it into each rule in the plugin.
pmcelhaney: @platinumazure Done. Thanks for merging #9785!

Patch:
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index bfd16e8d2..b72425262 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -27,6 +27,7 @@ While ESLint is designed to be run on the command line, it's possible to use ESL
     * [getFormatter()](#clienginegetformatter)
     * [getErrorResults()](#clienginegeterrorresults)
     * [outputFixes()](#cliengineoutputfixes)
+    * [getRules()](#clienginegetrules)
     * [version](#cliengineversion)
 * [RuleTester](#ruletester)
     * [Customizing RuleTester](#customizing-ruletester)
@@ -732,6 +733,26 @@ var report = cli.executeOnFiles(["myfile.js", "lib/"]);
 CLIEngine.outputFixes(report);
 ```
 
+### CLIEngine#getRules()
+
+This method returns a map of all loaded rules. Under the hood, it calls [Linter#getRules](#lintergetrules).
+
+```js
+const CLIEngine = require("eslint").CLIEngine;
+const cli = new CLIEngine();
+
+cli.getRules();
+
+/*
+Map {
+  'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] },
+  'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] },
+  ...
+}
+*/
+```
+
+
 ### CLIEngine.version
 
 `CLIEngine` has a static `version` property containing the semantic version number of ESLint that it comes from.
diff --git a/docs/rules/no-extra-parens.md b/docs/rules/no-extra-parens.md
index 392ac06a7..61fb50dc3 100644
--- a/docs/rules/no-extra-parens.md
+++ b/docs/rules/no-extra-parens.md
@@ -36,6 +36,12 @@ a = (b * c);
 
 (a * b) + c;
 
+for (a in (b, c));
+
+for (a in (b));
+
+for (a of (b));
+
 typeof (a);
 
 (function(){} ? a() : b());
@@ -55,6 +61,14 @@ Examples of **correct** code for this rule with the default `"all"` option:
 (function(){}) ? a() : b();
 
 (/^a$/).test(x);
+
+for (a of (b, c));
+
+for (a of b);
+
+for (a in b, c);
+
+for (a in b);
 ```
 
 ### conditionalAssign
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index 55450fd63..0c1afcbce 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -420,6 +420,10 @@ class CLIEngine {
         this.config = new Config(this.options, this.linter);
     }
 
+    getRules() {
+        return this.linter.getRules();
+    }
+
     /**
      * Returns results that only contains errors.
      * @param {LintResult[]} results The results to filter.
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index d8e0df64a..1025aa5a9 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -582,7 +582,7 @@ module.exports = {
                         tokensToIgnore.add(firstLeftToken);
                     }
                 }
-                if (hasExcessParens(node.right)) {
+                if (!(node.type === "ForOfStatement" && node.right.type === "SequenceExpression") && hasExcessParens(node.right)) {
                     report(node.right);
                 }
                 if (hasExcessParens(node.left)) {


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index aae66301d..7e69c9619 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -131,13 +131,31 @@ const DESCRIBE = Symbol("describe");
 const IT = Symbol("it");
 
 /**
- * This is `it` or `describe` if those don't exist.
+ * This is `it` default handler if `it` don't exist.
  * @this {Mocha}
  * @param {string} text - The description of the test case.
  * @param {Function} method - The logic of the test case.
  * @returns {any} Returned value of `method`.
  */
-function defaultHandler(text, method) {
+function itDefaultHandler(text, method) {
+    try {
+        return method.apply(this);
+    } catch (err) {
+        if (err instanceof assert.AssertionError) {
+            err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`;
+        }
+        throw err;
+    }
+}
+
+/**
+ * This is `describe` default handler if `describe` don't exist.
+ * @this {Mocha}
+ * @param {string} text - The description of the test case.
+ * @param {Function} method - The logic of the test case.
+ * @returns {any} Returned value of `method`.
+ */
+function describeDefaultHandler(text, method) {
     return method.apply(this);
 }
 
@@ -212,7 +230,7 @@ class RuleTester {
     static get describe() {
         return (
             this[DESCRIBE] ||
-            (typeof describe === "function" ? describe : defaultHandler)
+            (typeof describe === "function" ? describe : describeDefaultHandler)
         );
     }
 
@@ -223,7 +241,7 @@ class RuleTester {
     static get it() {
         return (
             this[IT] ||
-            (typeof it === "function" ? it : defaultHandler)
+            (typeof it === "function" ? it : itDefaultHandler)
         );
     }
 
diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js
index ec5af55e7..48bc82f10 100644
--- a/tests/lib/cli-engine.js
+++ b/tests/lib/cli-engine.js
@@ -2935,6 +2935,15 @@ describe("CLIEngine", () => {
 
     });
 
+    describe("getRules()", () => {
+        it("should expose the list of rules", () => {
+            const engine = new CLIEngine();
+
+            assert.isTrue(engine.getRules().has("no-eval"), "no-eval is present");
+
+        });
+    });
+
     describe("resolveFileGlobPatterns", () => {
 
         leche.withData([
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 7753c7d66..cab241a0a 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -145,7 +145,9 @@ ruleTester.run("no-extra-parens", rule, {
         "do; while(a);",
         "for(;;);",
         "for(a in b);",
+        "for(a in b, c);",
         "for(a of b);",
+        "for (a of (b, c));",
         "var a = (b, c);",
         "[]",
         "[a, b]",
@@ -1035,6 +1037,7 @@ ruleTester.run("no-extra-parens", rule, {
             "for (let.foo.bar in baz);",
             "Identifier",
             1
-        )
+        ),
+        invalid("for (a in (b, c));", "for (a in b, c);", "SequenceExpression", null)
     ]
 });
diff --git a/tests/lib/testers/no-test-runners.js b/tests/lib/testers/no-test-runners.js
new file mode 100644
index 000000000..b77c6bb90
--- /dev/null
+++ b/tests/lib/testers/no-test-runners.js
@@ -0,0 +1,35 @@
+/**
+ * @fileoverview Tests for RuleTester without any test runner
+ * @author Weijia Wang <starkwang@126.com>
+ */
+"use strict";
+
+/* global describe, it */
+/* eslint-disable no-global-assign*/
+const assert = require("assert"),
+    RuleTester = require("../../../lib/testers/rule-tester");
+const tmpIt = it;
+const tmpDescribe = describe;
+
+it = null;
+describe = null;
+
+try {
+    const ruleTester = new RuleTester();
+
+    assert.throws(() => {
+        ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+            valid: [
+                "bar = baz;"
+            ],
+            invalid: [
+                { code: "var foo = bar;", output: "invalid output", errors: 1 }
+            ]
+        });
+    }, /Output is incorrect\. \(' foo = bar;' == 'invalid output'\)$/);
+} catch (e) {
+    throw e;
+} finally {
+    it = tmpIt;
+    describe = tmpDescribe;
+}