Repository: eslint/eslint (js)

Issue:
Update --init behavior
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for requesting a change that is not a bug fix, rule change, or new rule. If you are here for another reason, please see below:

    1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
    2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
    3. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**The problem you want to solve.**

The `--init` behavior has remained pretty consistent since it was first launched. At that point, we didn't know what options people wanted and the number of rules was very small. It seems like the current questions don't make sense for how ESLint is used today. For example, forcing people to pick a style guide doesn't make sense for everyone and sends a message that ESLint is used primarily for styling. I'd like to update the behavior of `--init` to better reflect current users expectations and usage.

**Your take on the correct solution to problem.**

These are the ways I'd like to change `--init`:

1. **Assume `ecmaVersion` is 2018.** We first asked which version of ECMAScript they were using when ES6 was just being implemented. At this point, I think it's fair to assume people always want the latest (they can change it in their config if not).
2. **Ask how people want to use ESLint.** Instead of asking if users want to configure a style guide, ask "How do you want to use ESLint?", with the following options :
  * To check syntax only
  * To check syntax and find problems
  * To check syntax, find problems, and enforce code style
3. **Ask for framework question to replace JSX/React question.** Instead of asking if people are using JSX and then if they are using React, I'd create one question that asks if they are using either React (which implies JSX) or Vue (because there's a helpful plugin for Vue and it's pretty popular now). 

Optional:

* **Remove autoconfig option.** I'm not sure how people feel about this, but I'm skeptical that this option is used all that much thanks to the popularity of shareable configs. I'm in favor of removing it if others agree but have no problem leaving it in if people feel it's still helpful. 

**Are you willing to submit a pull request to implement this change?**

Yes.

Pull request:
Update: Behavior of --init (fixes #11105)
<!--
    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
[x] Other, please explain:

Updates the behavior of `--init` per #11105
<!--
    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)**

Rearranged the questions that are asked using `--init`. We now assume the latest version of ECMAScript is desired.

Also, there was a bug where if you chose a style guide the resulting config would have nothing but the resulting style guide. I fixed that.

The majority of the work was in rearranging the order of the questions. That part is pretty hard to follow but I did my best to clean it up for this round of changes. If we decide to remove autoconfig, I'd do some deeper cleaning.

In addition to the unit tests, I manually tested that each path through the questionnaire works locally.

**Is there anything you'd like reviewers to focus on?**

Anything that I missed?


Patch:
diff --git a/README.md b/README.md
index 847f5ac34..5c4267dfe 100644
--- a/README.md
+++ b/README.md
@@ -266,7 +266,8 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <!-- NOTE: This section is autogenerated. Do not manually edit.-->
 <!--sponsorsstart-->
 <h3>Gold Sponsors</h3>
-<p><a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fres.cloudinary.com%2Fopencollective%2Fimage%2Fupload%2Fv1508519428%2FS9gk78AS_400x400_fulq2l.jpg&height=96" alt="Facebook Open Source" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F098e3bd0-4d57-11e8-9324-0f6cc1f92bf1.png&height=96" alt="Airbnb" height="96"></a></p>
+<p><a href="https://code.facebook.com/projects/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fres.cloudinary.com%2Fopencollective%2Fimage%2Fupload%2Fv1508519428%2FS9gk78AS_400x400_fulq2l.jpg&height=96" alt="Facebook Open Source" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Fopencollective-production.s3-us-west-1.amazonaws.com%2F098e3bd0-4d57-11e8-9324-0f6cc1f92bf1.png&height=96" alt="Airbnb" height="96"></a></p><h3>Bronze Sponsors</h3>
+<p><a href="http://faithlife.com/ref/about"><img src="https://images.opencollective.com/proxy/images/?src=https%3A%2F%2Flogo.clearbit.com%2Ffaithlife.com&height=96" alt="Faithlife" height="32"></a></p>
 <!--sponsorsend-->
 
 ## Technology Sponsors
diff --git a/lib/config/config-initializer.js b/lib/config/config-initializer.js
index a6791ba85..b8126d473 100644
--- a/lib/config/config-initializer.js
+++ b/lib/config/config-initializer.js
@@ -3,6 +3,7 @@
  * @author Ilya Volodin
  */
 
+
 "use strict";
 
 //------------------------------------------------------------------------------
@@ -28,6 +29,8 @@ const debug = require("debug")("eslint:config-initializer");
 // Private
 //------------------------------------------------------------------------------
 
+const DEFAULT_ECMA_VERSION = 2018;
+
 /* istanbul ignore next: hard to test fs function */
 /**
  * Create .eslintrc file in the current working directory
@@ -239,43 +242,65 @@ function configureRules(answers, config) {
  * @returns {Object} config object
  */
 function processAnswers(answers) {
-    let config = { rules: {}, env: {}, parserOptions: {} };
+    let config = {
+        rules: {},
+        env: {},
+        parserOptions: {},
+        extends: []
+    };
 
-    config.parserOptions.ecmaVersion = answers.ecmaVersion;
-    if (answers.ecmaVersion >= 2015) {
-        if (answers.modules) {
-            config.parserOptions.sourceType = "module";
-        }
-        config.env.es6 = true;
-    }
+    // set the latest ECMAScript version
+    config.parserOptions.ecmaVersion = DEFAULT_ECMA_VERSION;
+    config.env.es6 = true;
+    config.globals = {
+        Atomics: "readonly",
+        SharedArrayBuffer: "readonly"
+    };
 
-    if (answers.commonjs) {
+    // set the module type
+    if (answers.moduleType === "esm") {
+        config.parserOptions.sourceType = "module";
+    } else if (answers.moduleType === "commonjs") {
         config.env.commonjs = true;
     }
+
+    // add in browser and node environments if necessary
     answers.env.forEach(env => {
         config.env[env] = true;
     });
-    if (answers.jsx) {
-        config.parserOptions = config.parserOptions || {};
-        config.parserOptions.ecmaFeatures = config.parserOptions.ecmaFeatures || {};
-        config.parserOptions.ecmaFeatures.jsx = true;
-        if (answers.react) {
-            config.plugins = ["react"];
-            config.parserOptions.ecmaVersion = 2018;
-        }
+
+    // add in library information
+    if (answers.framework === "react") {
+        config.parserOptions.ecmaFeatures = {
+            jsx: true
+        };
+        config.plugins = ["react"];
+    } else if (answers.framework === "vue") {
+        config.plugins = ["vue"];
+        config.extends.push("plugin:vue/essential");
     }
 
-    if (answers.source === "prompt") {
-        config.extends = "eslint:recommended";
-        config.rules.indent = ["error", answers.indent];
-        config.rules.quotes = ["error", answers.quotes];
-        config.rules["linebreak-style"] = ["error", answers.linebreak];
-        config.rules.semi = ["error", answers.semi ? "always" : "never"];
+    // setup rules based on problems/style enforcement preferences
+    if (answers.purpose === "problems") {
+        config.extends.unshift("eslint:recommended");
+    } else if (answers.purpose === "style") {
+        if (answers.source === "prompt") {
+            config.extends.unshift("eslint:recommended");
+            config.rules.indent = ["error", answers.indent];
+            config.rules.quotes = ["error", answers.quotes];
+            config.rules["linebreak-style"] = ["error", answers.linebreak];
+            config.rules.semi = ["error", answers.semi ? "always" : "never"];
+        } else if (answers.source === "auto") {
+            config = configureRules(answers, config);
+            config = autoconfig.extendFromRecommended(config);
+        }
     }
 
-    if (answers.source === "auto") {
-        config = configureRules(answers, config);
-        config = autoconfig.extendFromRecommended(config);
+    // normalize extends
+    if (config.extends.length === 0) {
+        delete config.extends;
+    } else if (config.extends.length === 1) {
+        config.extends = config.extends[0];
     }
 
     ConfigOps.normalizeToStrings(config);
@@ -324,7 +349,7 @@ function getLocalESLintVersion() {
  * @returns {string} The shareable config name.
  */
 function getStyleGuideName(answers) {
-    if (answers.styleguide === "airbnb" && !answers.airbnbReact) {
+    if (answers.styleguide === "airbnb" && answers.framework !== "react") {
         return "airbnb-base";
     }
     return answers.styleguide;
@@ -417,16 +442,62 @@ function askInstallModules(modules, packageJsonExists) {
 function promptUser() {
 
     return inquirer.prompt([
+        {
+            type: "list",
+            name: "purpose",
+            message: "How would you like to use ESLint?",
+            default: "problems",
+            choices: [
+                { name: "To check syntax only", value: "syntax" },
+                { name: "To check syntax and find problems", value: "problems" },
+                { name: "To check syntax, find problems, and enforce code style", value: "style" }
+            ]
+        },
+        {
+            type: "list",
+            name: "moduleType",
+            message: "What type of modules does your project use?",
+            default: "esm",
+            choices: [
+                { name: "JavaScript modules (import/export)", value: "esm" },
+                { name: "CommonJS (require/exports)", value: "commonjs" },
+                { name: "None of these", value: "none" }
+            ]
+        },
+        {
+            type: "list",
+            name: "framework",
+            message: "Which framework does your project use?",
+            default: "react",
+            choices: [
+                { name: "React", value: "react" },
+                { name: "Vue.js", value: "vue" },
+                { name: "None of these", value: "none" }
+            ]
+        },
+        {
+            type: "checkbox",
+            name: "env",
+            message: "Where does your code run?",
+            default: ["browser"],
+            choices: [
+                { name: "Browser", value: "browser" },
+                { name: "Node", value: "node" }
+            ]
+        },
         {
             type: "list",
             name: "source",
-            message: "How would you like to configure ESLint?",
-            default: "prompt",
+            message: "How would you like to define a style for your project?",
+            default: "guide",
             choices: [
                 { name: "Use a popular style guide", value: "guide" },
                 { name: "Answer questions about your style", value: "prompt" },
                 { name: "Inspect your JavaScript file(s)", value: "auto" }
-            ]
+            ],
+            when(answers) {
+                return answers.purpose === "style";
+            }
         },
         {
             type: "list",
@@ -442,15 +513,6 @@ function promptUser() {
                 return answers.source === "guide" && answers.packageJsonExists;
             }
         },
-        {
-            type: "confirm",
-            name: "airbnbReact",
-            message: "Do you use React?",
-            default: false,
-            when(answers) {
-                return answers.styleguide === "airbnb";
-            }
-        },
         {
             type: "input",
             name: "patterns",
@@ -470,10 +532,7 @@ function promptUser() {
             name: "format",
             message: "What format do you want your config file to be in?",
             default: "JavaScript",
-            choices: ["JavaScript", "YAML", "JSON"],
-            when(answers) {
-                return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
-            }
+            choices: ["JavaScript", "YAML", "JSON"]
         },
         {
             type: "confirm",
@@ -492,6 +551,15 @@ function promptUser() {
         }
     ]).then(earlyAnswers => {
 
+        // early exit if no style guide is necessary
+        if (earlyAnswers.purpose !== "style") {
+            const config = processAnswers(earlyAnswers);
+            const modules = getModulesList(config);
+
+            return askInstallModules(modules, earlyAnswers.packageJsonExists)
+                .then(() => writeFile(config, earlyAnswers.format));
+        }
+
         // early exit if you are using a style guide
         if (earlyAnswers.source === "guide") {
             if (!earlyAnswers.packageJsonExists) {
@@ -501,130 +569,69 @@ function promptUser() {
             if (earlyAnswers.installESLint === false && !semver.satisfies(earlyAnswers.localESLintVersion, earlyAnswers.requiredESLintVersionRange)) {
                 log.info(`Note: it might not work since ESLint's version is mismatched with the ${earlyAnswers.styleguide} config.`);
             }
-            if (earlyAnswers.styleguide === "airbnb" && !earlyAnswers.airbnbReact) {
+            if (earlyAnswers.styleguide === "airbnb" && earlyAnswers.framework !== "react") {
                 earlyAnswers.styleguide = "airbnb-base";
             }
 
-            const config = getConfigForStyleGuide(earlyAnswers.styleguide);
+            const config = ConfigOps.merge(processAnswers(earlyAnswers), getConfigForStyleGuide(earlyAnswers.styleguide));
             const modules = getModulesList(config);
 
             return askInstallModules(modules, earlyAnswers.packageJsonExists)
                 .then(() => writeFile(config, earlyAnswers.format));
+
         }
 
-        // continue with the questions otherwise...
+        if (earlyAnswers.source === "auto") {
+            const combinedAnswers = Object.assign({}, earlyAnswers);
+            const config = processAnswers(combinedAnswers);
+            const modules = getModulesList(config);
+
+            return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format));
+        }
+
+        // continue with the style questions otherwise...
         return inquirer.prompt([
             {
                 type: "list",
-                name: "ecmaVersion",
-                message: "Which version of ECMAScript do you use?",
-                choices: [
-                    { name: "ES3", value: 3 },
-                    { name: "ES5", value: 5 },
-                    { name: "ES2015", value: 2015 },
-                    { name: "ES2016", value: 2016 },
-                    { name: "ES2017", value: 2017 },
-                    { name: "ES2018", value: 2018 }
-                ],
-                default: 1 // This is the index in the choices list
+                name: "indent",
+                message: "What style of indentation do you use?",
+                default: "tab",
+                choices: [{ name: "Tabs", value: "tab" }, { name: "Spaces", value: 4 }]
             },
             {
-                type: "confirm",
-                name: "modules",
-                message: "Are you using ES6 modules?",
-                default: false,
-                when(answers) {
-                    return answers.ecmaVersion >= 2015;
-                }
-            },
-            {
-                type: "checkbox",
-                name: "env",
-                message: "Where will your code run?",
-                default: ["browser"],
-                choices: [{ name: "Browser", value: "browser" }, { name: "Node", value: "node" }]
+                type: "list",
+                name: "quotes",
+                message: "What quotes do you use for strings?",
+                default: "double",
+                choices: [{ name: "Double", value: "double" }, { name: "Single", value: "single" }]
             },
             {
-                type: "confirm",
-                name: "commonjs",
-                message: "Do you use CommonJS?",
-                default: false,
-                when(answers) {
-                    return answers.env.some(env => env === "browser");
-                }
+                type: "list",
+                name: "linebreak",
+                message: "What line endings do you use?",
+                default: "unix",
+                choices: [{ name: "Unix", value: "unix" }, { name: "Windows", value: "windows" }]
             },
             {
                 type: "confirm",
-                name: "jsx",
-                message: "Do you use JSX?",
-                default: false
+                name: "semi",
+                message: "Do you require semicolons?",
+                default: true
             },
             {
-                type: "confirm",
-                name: "react",
-                message: "Do you use React?",
-                default: false,
-                when(answers) {
-                    return answers.jsx;
-                }
-            }
-        ]).then(secondAnswers => {
-
-            // early exit if you are using automatic style generation
-            if (earlyAnswers.source === "auto") {
-                const combinedAnswers = Object.assign({}, earlyAnswers, secondAnswers);
-
-                const config = processAnswers(combinedAnswers);
-
-                const modules = getModulesList(config);
-
-                return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format));
+                type: "list",
+                name: "format",
+                message: "What format do you want your config file to be in?",
+                default: "JavaScript",
+                choices: ["JavaScript", "YAML", "JSON"]
             }
+        ]).then(answers => {
+            const totalAnswers = Object.assign({}, earlyAnswers, answers);
 
-            // continue with the style questions otherwise...
-            return inquirer.prompt([
-                {
-                    type: "list",
-                    name: "indent",
-                    message: "What style of indentation do you use?",
-                    default: "tab",
-                    choices: [{ name: "Tabs", value: "tab" }, { name: "Spaces", value: 4 }]
-                },
-                {
-                    type: "list",
-                    name: "quotes",
-                    message: "What quotes do you use for strings?",
-                    default: "double",
-                    choices: [{ name: "Double", value: "double" }, { name: "Single", value: "single" }]
-                },
-                {
-                    type: "list",
-                    name: "linebreak",
-                    message: "What line endings do you use?",
-                    default: "unix",
-                    choices: [{ name: "Unix", value: "unix" }, { name: "Windows", value: "windows" }]
-                },
-                {
-                    type: "confirm",
-                    name: "semi",
-                    message: "Do you require semicolons?",
-                    default: true
-                },
-                {
-                    type: "list",
-                    name: "format",
-                    message: "What format do you want your config file to be in?",
-                    default: "JavaScript",
-                    choices: ["JavaScript", "YAML", "JSON"]
-                }
-            ]).then(answers => {
-                const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers);
-
-                const config = processAnswers(totalAnswers);
-                const modules = getModulesList(config);
+            const config = processAnswers(totalAnswers);
+            const modules = getModulesList(config);
 
-                return askInstallModules(modules).then(() => writeFile(config, answers.format));
-            });
+            return askInstallModules(modules).then(() => writeFile(config, answers.format));
         });
     });
 }


Test patch:
diff --git a/tests/lib/config/config-initializer.js b/tests/lib/config/config-initializer.js
index 3767fe376..d78651b1b 100644
--- a/tests/lib/config/config-initializer.js
+++ b/tests/lib/config/config-initializer.js
@@ -117,20 +117,17 @@ describe("configInitializer", () => {
 
             beforeEach(() => {
                 answers = {
+                    purpose: "style",
                     source: "prompt",
                     extendDefault: true,
                     indent: 2,
                     quotes: "single",
                     linebreak: "unix",
                     semi: true,
-                    ecmaVersion: 2015,
-                    modules: true,
+                    moduleType: "esm",
                     es6Globals: true,
                     env: ["browser"],
-                    jsx: false,
-                    react: false,
-                    format: "JSON",
-                    commonjs: false
+                    format: "JSON"
                 };
             });
 
@@ -142,7 +139,9 @@ describe("configInitializer", () => {
                 assert.deepStrictEqual(config.rules["linebreak-style"], ["error", "unix"]);
                 assert.deepStrictEqual(config.rules.semi, ["error", "always"]);
                 assert.strictEqual(config.env.es6, true);
-                assert.strictEqual(config.parserOptions.ecmaVersion, 2015);
+                assert.strictEqual(config.globals.Atomics, "readonly");
+                assert.strictEqual(config.globals.SharedArrayBuffer, "readonly");
+                assert.strictEqual(config.parserOptions.ecmaVersion, 2018);
                 assert.strictEqual(config.parserOptions.sourceType, "module");
                 assert.strictEqual(config.env.browser, true);
                 assert.strictEqual(config.extends, "eslint:recommended");
@@ -155,16 +154,8 @@ describe("configInitializer", () => {
                 assert.deepStrictEqual(config.rules.semi, ["error", "never"]);
             });
 
-            it("should enable jsx flag", () => {
-                answers.jsx = true;
-                const config = init.processAnswers(answers);
-
-                assert.strictEqual(config.parserOptions.ecmaFeatures.jsx, true);
-            });
-
             it("should enable react plugin", () => {
-                answers.jsx = true;
-                answers.react = true;
+                answers.framework = "react";
                 const config = init.processAnswers(answers);
 
                 assert.strictEqual(config.parserOptions.ecmaFeatures.jsx, true);
@@ -172,12 +163,13 @@ describe("configInitializer", () => {
                 assert.deepStrictEqual(config.plugins, ["react"]);
             });
 
-            it("should not enable es6", () => {
-                answers.ecmaVersion = 5;
+            it("should enable vue plugin", () => {
+                answers.framework = "vue";
                 const config = init.processAnswers(answers);
 
-                assert.strictEqual(config.parserOptions.ecmaVersion, 5);
-                assert.isUndefined(config.env.es6);
+                assert.strictEqual(config.parserOptions.ecmaVersion, 2018);
+                assert.deepStrictEqual(config.plugins, ["vue"]);
+                assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:vue/essential"]);
             });
 
             it("should extend eslint:recommended", () => {
@@ -193,7 +185,7 @@ describe("configInitializer", () => {
             });
 
             it("should use commonjs when set", () => {
-                answers.commonjs = true;
+                answers.moduleType = "commonjs";
                 const config = init.processAnswers(answers);
 
                 assert.isTrue(config.env.commonjs);
@@ -338,14 +330,11 @@ describe("configInitializer", () => {
                 ].join(" ");
 
                 answers = {
+                    purpose: "style",
                     source: "auto",
                     patterns,
-                    ecmaVersion: 5,
                     env: ["browser"],
-                    jsx: false,
-                    react: false,
-                    format: "JSON",
-                    commonjs: false
+                    format: "JSON"
                 };
 
                 sandbox = sinon.sandbox.create();