From 51c497298a15ad296a2b1f8fc397df687976b836 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 14 Feb 2019 14:04:32 -0700 Subject: [PATCH] Update: Behavior of --init (fixes #11105) (#11332) * Update: Reorder init menu (fixes #11105) * Update: Behavior of --init (fixes #11105) * Update Vue settings * Add missing globals * Update names of module options * false -> readonly * Fix package.json merge conflict --- lib/config/config-initializer.js | 293 +++++++++++++------------ tests/lib/config/config-initializer.js | 41 ++-- 2 files changed, 165 insertions(+), 169 deletions(-) diff --git a/lib/config/config-initializer.js b/lib/config/config-initializer.js index a6791ba8530..b8126d4735c 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)); }); }); } diff --git a/tests/lib/config/config-initializer.js b/tests/lib/config/config-initializer.js index 3767fe376f3..d78651b1b45 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();