From 3e293ec4e355cabe873448f4947776f3b94a2ebd Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 21 Feb 2021 00:47:20 +0100 Subject: [PATCH 1/4] One config to rule them all --- .eslintrc.base.js | 19 +++-- .eslintrc.js | 12 +-- .prettierrc.json | 3 +- @typescript-eslint.js | 22 ------ README.md | 50 ++---------- babel.js | 10 --- bin/cli.js | 97 ++++++++++++----------- bin/validators.js | 13 +-- flowtype.js | 15 ---- index.js | 97 ++++++++++++++++++++++- react.js | 28 ------- scripts/build.js | 5 +- standard.js | 9 --- test-lint/{index.js => core.js} | 0 test/cli.test.js | 19 +++-- test/lint-verify-fail.test.js | 31 ++++++-- test/rules.test.js | 135 ++++++++++++++++---------------- unicorn.js | 9 --- vue.js | 43 ---------- 19 files changed, 267 insertions(+), 350 deletions(-) delete mode 100644 @typescript-eslint.js delete mode 100644 babel.js delete mode 100644 flowtype.js delete mode 100644 react.js delete mode 100644 standard.js rename test-lint/{index.js => core.js} (100%) delete mode 100644 unicorn.js delete mode 100644 vue.js diff --git a/.eslintrc.base.js b/.eslintrc.base.js index bf04b9e..d3c4e86 100644 --- a/.eslintrc.base.js +++ b/.eslintrc.base.js @@ -3,7 +3,7 @@ // This file is only used in `./.eslintrc.js` and in the tests – it’s not part // of the eslint-config-prettier npm package. -const fs = require("fs"); +const config = require("."); module.exports = { extends: [ @@ -15,13 +15,12 @@ module.exports = { ], plugins: [ "prettier", - ...fs - .readdirSync(__dirname) - .filter( - (file) => - !file.startsWith(".") && file.endsWith(".js") && file !== "index.js" - ) - .map((ruleFileName) => ruleFileName.replace(/\.js$/, "")), + ...new Set( + Object.keys(config.rules) + .map((ruleName) => ruleName.split("/")) + .filter((parts) => parts.length > 1) + .map((parts) => parts[0]) + ), ], parserOptions: { parser: "babel-eslint", @@ -38,10 +37,10 @@ module.exports = { node: true, }, rules: { - indent: "off", + "indent": "off", "linebreak-style": "off", "no-dupe-keys": "error", - strict: "error", + "strict": "error", "prefer-spread": "off", "require-jsdoc": "off", "prettier/prettier": "error", diff --git a/.eslintrc.js b/.eslintrc.js index 4742e6a..2d96178 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,16 +6,8 @@ // as an “eat your own dogfood” test. That feels like a good test, but // complicates things a little sometimes. -const fs = require("fs"); - module.exports = { - extends: [ - "./.eslintrc.base.js", - ...fs - .readdirSync(__dirname) - .filter((file) => !file.startsWith(".") && file.endsWith(".js")) - .map((ruleFileName) => `./${ruleFileName}`), - ], + extends: ["./.eslintrc.base.js", "./index.js", "./prettier.js"], rules: { "prettier/prettier": "off", }, @@ -32,7 +24,7 @@ module.exports = { "The comma operator is confusing and a common mistake. Don’t use it!", }, ], - quotes: [ + "quotes": [ "error", "double", { avoidEscape: true, allowTemplateLiterals: false }, diff --git a/.prettierrc.json b/.prettierrc.json index d2504b4..27e719e 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,3 +1,4 @@ { - "proseWrap": "never" + "proseWrap": "never", + "quoteProps": "consistent" } diff --git a/@typescript-eslint.js b/@typescript-eslint.js deleted file mode 100644 index c7cd8ee..0000000 --- a/@typescript-eslint.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; - -module.exports = { - rules: { - "@typescript-eslint/quotes": 0, - - "@typescript-eslint/brace-style": "off", - "@typescript-eslint/comma-dangle": "off", - "@typescript-eslint/comma-spacing": "off", - "@typescript-eslint/func-call-spacing": "off", - "@typescript-eslint/indent": "off", - "@typescript-eslint/keyword-spacing": "off", - "@typescript-eslint/member-delimiter-style": "off", - "@typescript-eslint/no-extra-parens": "off", - "@typescript-eslint/no-extra-semi": "off", - "@typescript-eslint/object-curly-spacing": "off", - "@typescript-eslint/semi": "off", - "@typescript-eslint/space-before-function-paren": "off", - "@typescript-eslint/space-infix-ops": "off", - "@typescript-eslint/type-annotation-spacing": "off", - }, -}; diff --git a/README.md b/README.md index 0aba3be..801b20b 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Then, add eslint-config-prettier to the "extends" array in your `.eslintrc.*` fi } ``` -A few ESLint plugins are supported as well: +That’s it! Extending `"prettier"` turns off a bunch of core ESLint rules, as well as a few rules from these plugins: - [@typescript-eslint/eslint-plugin] - [eslint-plugin-babel] @@ -68,40 +68,7 @@ A few ESLint plugins are supported as well: - [eslint-plugin-unicorn] - [eslint-plugin-vue] -Add extra exclusions for the plugins you use like so: - - -```json -{ - "extends": [ - "some-other-config-you-use", - "prettier", - "prettier/@typescript-eslint", - "prettier/babel", - "prettier/flowtype", - "prettier/prettier", - "prettier/react", - "prettier/standard", - "prettier/unicorn", - "prettier/vue" - ] -} -``` - -If you extend a config which uses a plugin, it is recommended to add `"prettier/that-plugin"` (if available). For example, [eslint-config-airbnb] enables [eslint-plugin-react] rules, so `"prettier/react"` is needed: - - -```json -{ - "extends": [ - "airbnb", - "prettier", - "prettier/react" - ] -} -``` - -If you’re unsure which plugins are used, you can usually find them in your `package.json`. +> Note: You might find guides on the Internet that say you should extend stuff like `"prettier/react"`. Since v8.0.0 of eslint-config-prettier, all you need is to extend `"prettier"`! ### Excluding deprecated rules @@ -698,17 +665,11 @@ Have new rules been added since those versions? Have we missed any rules? Is the If you’d like to add support for eslint-plugin-foobar, this is how you’d go about it: -First, create `foobar.js`: +First, add rules to `index.js`: ```js -"use strict"; - -module.exports = { - rules: { - "foobar/some-rule": "off" - } -}; +"foobar/some-rule": "off" ``` Then, create `test-lint/foobar.js`: @@ -729,7 +690,7 @@ Finally, you need to mention the plugin in several places: - Add eslint-plugin-foobar to the "devDependencies" field in `package.json`. - Make sure that at least one rule from eslint-plugin-foobar gets used in `.eslintrc.base.js`. -- Add it to the list of supported plugins and to the Contributing section in `README.md`. +- Add it to the lists of supported plugins and in this `README.md`. When you’re done, run `npm test` to verify that you got it all right. It runs several other npm scripts: @@ -751,7 +712,6 @@ When you’re done, run `npm test` to verify that you got it all right. It runs [arrow-body-style]: https://eslint.org/docs/rules/arrow-body-style [babel/quotes]: https://github.com/babel/eslint-plugin-babel#rules [curly]: https://eslint.org/docs/rules/curly -[eslint-config-airbnb]: https://www.npmjs.com/package/eslint-config-airbnb [eslint-plugin-babel]: https://github.com/babel/eslint-plugin-babel [eslint-plugin-flowtype]: https://github.com/gajus/eslint-plugin-flowtype [eslint-plugin-prettier-autofix-issue]: https://github.com/prettier/eslint-plugin-prettier#arrow-body-style-and-prefer-arrow-callback-issue diff --git a/babel.js b/babel.js deleted file mode 100644 index 47d0365..0000000 --- a/babel.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; - -module.exports = { - rules: { - "babel/quotes": 0, - - "babel/object-curly-spacing": "off", - "babel/semi": "off", - }, -}; diff --git a/bin/cli.js b/bin/cli.js index 8d5d74e..6ee12a3 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -2,9 +2,9 @@ "use strict"; -const fs = require("fs"); -const path = require("path"); const validators = require("./validators"); +const config = require(".."); +const prettier = require("../prettier"); // Require locally installed eslint, for `npx eslint-config-prettier` support // with no local eslint-config-prettier installation. @@ -15,6 +15,9 @@ const { ESLint } = require(require.resolve("eslint", { const SPECIAL_RULES_URL = "https://github.com/prettier/eslint-config-prettier#special-rules"; +const PRETTIER_RULES_URL = + "https://github.com/prettier/eslint-config-prettier#arrow-body-style-and-prefer-arrow-callback"; + if (module === require.main) { const args = process.argv.slice(2); @@ -28,8 +31,8 @@ if (module === require.main) { Promise.all(args.map((file) => eslint.calculateConfigForFile(file))) .then((configs) => { const rules = [].concat( - ...configs.map((config, index) => - Object.entries(config.rules).map((entry) => [...entry, args[index]]) + ...configs.map(({ rules }, index) => + Object.entries(rules).map((entry) => [...entry, args[index]]) ) ); const result = processRules(rules); @@ -68,24 +71,13 @@ https://github.com/prettier/eslint-config-prettier#cli-helper-tool } function processRules(configRules) { - // This used to look at "files" in package.json, but that is not reliable due - // to an npm bug. See: - // https://github.com/prettier/eslint-config-prettier/issues/57 - const allRules = Object.assign( - Object.create(null), - ...fs - .readdirSync(path.join(__dirname, "..")) - .filter((name) => !name.startsWith(".") && name.endsWith(".js")) - .map((ruleFileName) => require(`../${ruleFileName}`).rules) - ); - - const regularRules = filterRules(allRules, (_, value) => value === "off"); + const regularRules = filterRules(config.rules, (_, value) => value === "off"); const optionsRules = filterRules( - allRules, + config.rules, (ruleName, value) => value === 0 && ruleName in validators ); const specialRules = filterRules( - allRules, + config.rules, (ruleName, value) => value === 0 && !(ruleName in validators) ); @@ -99,7 +91,7 @@ function processRules(configRules) { .filter(Boolean); const flaggedRules = enabledRules.filter( - ({ ruleName }) => ruleName in allRules + ({ ruleName }) => ruleName in config.rules ); const regularFlaggedRuleNames = filterRuleNames( @@ -109,37 +101,21 @@ function processRules(configRules) { const optionsFlaggedRuleNames = filterRuleNames( flaggedRules, ({ ruleName, ...rule }) => - ruleName in optionsRules && !validators[ruleName](rule, enabledRules) + ruleName in optionsRules && !validators[ruleName](rule) ); const specialFlaggedRuleNames = filterRuleNames( flaggedRules, ({ ruleName }) => ruleName in specialRules ); - - if ( - regularFlaggedRuleNames.length === 0 && - optionsFlaggedRuleNames.length === 0 - ) { - const baseMessage = - "No rules that are unnecessary or conflict with Prettier were found."; - - const message = - specialFlaggedRuleNames.length === 0 - ? baseMessage - : [ - baseMessage, - "", - "However, the following rules are enabled but cannot be automatically checked. See:", - SPECIAL_RULES_URL, - "", - printRuleNames(specialFlaggedRuleNames), - ].join("\n"); - - return { - stdout: message, - code: 0, - }; - } + const prettierFlaggedRuleNames = filterRuleNames( + enabledRules, + ({ ruleName, source }) => + ruleName in prettier.rules && + enabledRules.some( + (rule) => + rule.ruleName === "prettier/prettier" && rule.source === source + ) + ); const regularMessage = [ "The following rules are unnecessary or might conflict with Prettier:", @@ -161,10 +137,41 @@ function processRules(configRules) { printRuleNames(specialFlaggedRuleNames), ].join("\n"); + const prettierMessage = [ + "The following rules can cause issues when using eslint-plugin-prettier at the same time.", + "Only enable them if you know what you are doing! See:", + PRETTIER_RULES_URL, + "", + printRuleNames(prettierFlaggedRuleNames), + ].join("\n"); + + if ( + regularFlaggedRuleNames.length === 0 && + optionsFlaggedRuleNames.length === 0 + ) { + const message = + specialFlaggedRuleNames.length === 0 && + prettierFlaggedRuleNames.length === 0 + ? "No rules that are unnecessary or conflict with Prettier were found." + : [ + specialFlaggedRuleNames.length === 0 ? null : specialMessage, + prettierFlaggedRuleNames.length === 0 ? null : prettierMessage, + "Other than that, no rules that are unnecessary or conflict with Prettier were found.", + ] + .filter(Boolean) + .join("\n\n"); + + return { + stdout: message, + code: 0, + }; + } + const message = [ regularFlaggedRuleNames.length === 0 ? null : regularMessage, optionsFlaggedRuleNames.length === 0 ? null : optionsMessage, specialFlaggedRuleNames.length === 0 ? null : specialMessage, + prettierFlaggedRuleNames.length === 0 ? null : prettierMessage, ] .filter(Boolean) .join("\n\n"); diff --git a/bin/validators.js b/bin/validators.js index ef3eab4..f63bdad 100644 --- a/bin/validators.js +++ b/bin/validators.js @@ -4,9 +4,7 @@ // `false` if the options DO conflict with Prettier, and `true` if they don’t. module.exports = { - "arrow-body-style": checkEslintPluginPrettier, - - curly({ options }) { + "curly"({ options }) { if (options.length === 0) { return true; } @@ -50,8 +48,6 @@ module.exports = { return Boolean(firstOption && firstOption.allowIndentationTabs); }, - "prefer-arrow-callback": checkEslintPluginPrettier, - "vue/html-self-closing"({ options }) { if (options.length === 0) { return false; @@ -65,10 +61,3 @@ module.exports = { ); }, }; - -function checkEslintPluginPrettier({ source: currentSource }, enabledRules) { - return !enabledRules.some( - ({ ruleName, source }) => - ruleName === "prettier/prettier" && currentSource === source - ); -} diff --git a/flowtype.js b/flowtype.js deleted file mode 100644 index 34b8943..0000000 --- a/flowtype.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; - -module.exports = { - rules: { - "flowtype/boolean-style": "off", - "flowtype/delimiter-dangle": "off", - "flowtype/generic-spacing": "off", - "flowtype/object-type-delimiter": "off", - "flowtype/semi": "off", - "flowtype/space-after-type-colon": "off", - "flowtype/space-before-generic-bracket": "off", - "flowtype/space-before-type-colon": "off", - "flowtype/union-intersection-spacing": "off", - }, -}; diff --git a/index.js b/index.js index a6d9cb1..2a61c4c 100644 --- a/index.js +++ b/index.js @@ -7,14 +7,19 @@ module.exports = { // The following rules can be used in some cases. See the README for more // information. (These are marked with `0` instead of `"off"` so that a // script can distinguish them.) - curly: 0, + "curly": 0, "lines-around-comment": 0, "max-len": 0, "no-confusing-arrow": 0, "no-mixed-operators": 0, "no-tabs": 0, "no-unexpected-multiline": 0, - quotes: 0, + "quotes": 0, + "@typescript-eslint/quotes": 0, + "babel/quotes": 0, + "vue/html-self-closing": 0, + "vue/max-len": 0, + // The rest are rules that you never need to enable when using Prettier. "array-bracket-newline": "off", "array-bracket-spacing": "off", @@ -35,7 +40,7 @@ module.exports = { "generator-star": "off", "generator-star-spacing": "off", "implicit-arrow-linebreak": "off", - indent: "off", + "indent": "off", "jsx-quotes": "off", "key-spacing": "off", "keyword-spacing": "off", @@ -65,7 +70,7 @@ module.exports = { "padded-blocks": "off", "quote-props": "off", "rest-spread-spacing": "off", - semi: "off", + "semi": "off", "semi-spacing": "off", "semi-style": "off", "space-after-function-name": "off", @@ -87,6 +92,87 @@ module.exports = { "wrap-iife": "off", "wrap-regex": "off", "yield-star-spacing": "off", + "@typescript-eslint/brace-style": "off", + "@typescript-eslint/comma-dangle": "off", + "@typescript-eslint/comma-spacing": "off", + "@typescript-eslint/func-call-spacing": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/keyword-spacing": "off", + "@typescript-eslint/member-delimiter-style": "off", + "@typescript-eslint/no-extra-parens": "off", + "@typescript-eslint/no-extra-semi": "off", + "@typescript-eslint/object-curly-spacing": "off", + "@typescript-eslint/semi": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/space-infix-ops": "off", + "@typescript-eslint/type-annotation-spacing": "off", + "babel/object-curly-spacing": "off", + "babel/semi": "off", + "flowtype/boolean-style": "off", + "flowtype/delimiter-dangle": "off", + "flowtype/generic-spacing": "off", + "flowtype/object-type-delimiter": "off", + "flowtype/semi": "off", + "flowtype/space-after-type-colon": "off", + "flowtype/space-before-generic-bracket": "off", + "flowtype/space-before-type-colon": "off", + "flowtype/union-intersection-spacing": "off", + "react/jsx-child-element-spacing": "off", + "react/jsx-closing-bracket-location": "off", + "react/jsx-closing-tag-location": "off", + "react/jsx-curly-newline": "off", + "react/jsx-curly-spacing": "off", + "react/jsx-equals-spacing": "off", + "react/jsx-first-prop-new-line": "off", + "react/jsx-indent": "off", + "react/jsx-indent-props": "off", + "react/jsx-max-props-per-line": "off", + "react/jsx-newline": "off", + "react/jsx-one-expression-per-line": "off", + "react/jsx-props-no-multi-spaces": "off", + "react/jsx-tag-spacing": "off", + "react/jsx-wrap-multilines": "off", + "standard/array-bracket-even-spacing": "off", + "standard/computed-property-even-spacing": "off", + "standard/object-curly-even-spacing": "off", + "unicorn/empty-brace-spaces": "off", + "unicorn/no-nested-ternary": "off", + "unicorn/number-literal-case": "off", + "vue/array-bracket-newline": "off", + "vue/array-bracket-spacing": "off", + "vue/arrow-spacing": "off", + "vue/block-spacing": "off", + "vue/block-tag-newline": "off", + "vue/brace-style": "off", + "vue/comma-dangle": "off", + "vue/comma-spacing": "off", + "vue/comma-style": "off", + "vue/dot-location": "off", + "vue/func-call-spacing": "off", + "vue/html-closing-bracket-newline": "off", + "vue/html-closing-bracket-spacing": "off", + "vue/html-end-tags": "off", + "vue/html-indent": "off", + "vue/html-quotes": "off", + "vue/key-spacing": "off", + "vue/keyword-spacing": "off", + "vue/max-attributes-per-line": "off", + "vue/multiline-html-element-content-newline": "off", + "vue/mustache-interpolation-spacing": "off", + "vue/no-extra-parens": "off", + "vue/no-multi-spaces": "off", + "vue/no-spaces-around-equal-signs-in-attribute": "off", + "vue/object-curly-newline": "off", + "vue/object-curly-spacing": "off", + "vue/object-property-newline": "off", + "vue/operator-linebreak": "off", + "vue/script-indent": "off", + "vue/singleline-html-element-content-newline": "off", + "vue/space-in-parens": "off", + "vue/space-infix-ops": "off", + "vue/space-unary-ops": "off", + "vue/template-curly-spacing": "off", + ...(includeDeprecated && { // Deprecated since version 4.0.0. // https://github.com/eslint/eslint/pull/8286 @@ -94,6 +180,9 @@ module.exports = { // Deprecated since version 3.3.0. // https://eslint.org/docs/rules/no-spaced-func "no-spaced-func": "off", + // Deprecated since version 7.0.0. + // https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md#700---2017-05-06 + "react/jsx-space-before-closing": "off", }), }, }; diff --git a/react.js b/react.js deleted file mode 100644 index 297b17c..0000000 --- a/react.js +++ /dev/null @@ -1,28 +0,0 @@ -"use strict"; - -const includeDeprecated = !process.env.ESLINT_CONFIG_PRETTIER_NO_DEPRECATED; - -module.exports = { - rules: { - "react/jsx-child-element-spacing": "off", - "react/jsx-closing-bracket-location": "off", - "react/jsx-closing-tag-location": "off", - "react/jsx-curly-newline": "off", - "react/jsx-curly-spacing": "off", - "react/jsx-equals-spacing": "off", - "react/jsx-first-prop-new-line": "off", - "react/jsx-indent": "off", - "react/jsx-indent-props": "off", - "react/jsx-max-props-per-line": "off", - "react/jsx-newline": "off", - "react/jsx-one-expression-per-line": "off", - "react/jsx-props-no-multi-spaces": "off", - "react/jsx-tag-spacing": "off", - "react/jsx-wrap-multilines": "off", - ...(includeDeprecated && { - // Deprecated since version 7.0.0. - // https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md#700---2017-05-06 - "react/jsx-space-before-closing": "off", - }), - }, -}; diff --git a/scripts/build.js b/scripts/build.js index 8642d8c..27990d4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -16,14 +16,11 @@ const FILES_TO_COPY = [ src: "README.md", transform: (content) => content.replace(/^---[^]*/m, READ_MORE), }, + { src: "index.js" }, ...fs .readdirSync(path.join(DIR, "bin")) .filter((file) => !file.startsWith(".") && file.endsWith(".js")) .map((file) => ({ src: path.join("bin", file) })), - ...fs - .readdirSync(DIR) - .filter((file) => !file.startsWith(".") && file.endsWith(".js")) - .map((file) => ({ src: file })), ]; if (fs.existsSync(BUILD)) { diff --git a/standard.js b/standard.js deleted file mode 100644 index faacadd..0000000 --- a/standard.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -module.exports = { - rules: { - "standard/array-bracket-even-spacing": "off", - "standard/computed-property-even-spacing": "off", - "standard/object-curly-even-spacing": "off", - }, -}; diff --git a/test-lint/index.js b/test-lint/core.js similarity index 100% rename from test-lint/index.js rename to test-lint/core.js diff --git a/test/cli.test.js b/test/cli.test.js index 1b6d888..a1764f2 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -97,12 +97,12 @@ test("special rules", () => { expect(cli.processRules(createRules(rules, "error"))).toMatchInlineSnapshot(` Object { "code": 0, - "stdout": "No rules that are unnecessary or conflict with Prettier were found. - - However, the following rules are enabled but cannot be automatically checked. See: + "stdout": "The following rules are enabled but cannot be automatically checked. See: https://github.com/prettier/eslint-config-prettier#special-rules - - max-len", + - max-len + + Other than that, no rules that are unnecessary or conflict with Prettier were found.", } `); }); @@ -169,12 +169,15 @@ test("eslint-plugin-prettier", () => { ]) ).toMatchInlineSnapshot(` Object { - "code": 2, - "stdout": "The following rules are enabled with config that might conflict with Prettier. See: - https://github.com/prettier/eslint-config-prettier#special-rules + "code": 0, + "stdout": "The following rules can cause issues when using eslint-plugin-prettier at the same time. + Only enable them if you know what you are doing! See: + https://github.com/prettier/eslint-config-prettier#arrow-body-style-and-prefer-arrow-callback - arrow-body-style - - prefer-arrow-callback", + - prefer-arrow-callback + + Other than that, no rules that are unnecessary or conflict with Prettier were found.", } `); }); diff --git a/test/lint-verify-fail.test.js b/test/lint-verify-fail.test.js index 4e0c5c8..993ee18 100644 --- a/test/lint-verify-fail.test.js +++ b/test/lint-verify-fail.test.js @@ -4,10 +4,27 @@ const childProcess = require("child_process"); const fs = require("fs"); const path = require("path"); -const ROOT = path.join(__dirname, ".."); -const ruleFiles = fs - .readdirSync(ROOT) - .filter((name) => !name.startsWith(".") && name.endsWith(".js")); +const testLintFiles = fs + .readdirSync(path.join(__dirname, "..", "test-lint")) + .filter((name) => !name.startsWith(".")); + +function parseJson(result) { + try { + return JSON.parse(result.stdout); + } catch (error) { + throw new SyntaxError( + ` +${error.message} + +### stdout +${result.stdout} + +### stderr +${result.stderr} +`.trimStart() + ); + } +} describe("test-lint/ causes errors without eslint-config-prettier", () => { const result = childProcess.spawnSync( @@ -15,10 +32,10 @@ describe("test-lint/ causes errors without eslint-config-prettier", () => { ["run", "test:lint-verify-fail", "--silent"], { encoding: "utf8", shell: true } ); - const output = JSON.parse(result.stdout); + const output = parseJson(result); test("every test-lint/ file must cause an error", () => { - expect(output.length).toBe(ruleFiles.length); + expect(output.length).toBe(testLintFiles.length); }); output.forEach((data) => { @@ -34,7 +51,7 @@ describe("test-lint/ causes errors without eslint-config-prettier", () => { // ESLint core rules have no prefix. // eslint-plugin-prettier provides no conflicting rules, but makes two // core rules unusable. - if (name === "index" || name === "prettier") { + if (name === "core" || name === "prettier") { expect( ruleIds .filter((ruleId) => ruleId.includes("/")) diff --git a/test/rules.test.js b/test/rules.test.js index 8632acd..f281fc6 100644 --- a/test/rules.test.js +++ b/test/rules.test.js @@ -4,65 +4,86 @@ const childProcess = require("child_process"); const fs = require("fs"); const path = require("path"); const rimraf = require("rimraf"); +const config = require("../"); const eslintConfig = require("../.eslintrc"); const eslintConfigBase = require("../.eslintrc.base"); const ROOT = path.join(__dirname, ".."); const TEST_CONFIG_DIR = path.join(ROOT, "test-config"); -const ruleFiles = fs - .readdirSync(ROOT) - .filter((name) => !name.startsWith(".") && name.endsWith(".js")); -const configFiles = fs - .readdirSync(ROOT) - .filter((name) => name.startsWith(".eslintrc")); +const plugins = [ + ...new Set( + Object.keys(config.rules).map((ruleName) => { + const parts = ruleName.split("/"); + return parts.length > 1 ? parts[0] : "core"; + }) + ), +]; + +function parseJson(result) { + try { + return JSON.parse(result.stdout); + } catch (error) { + throw new SyntaxError( + ` +${error.message} + +### stdout +${result.stdout} + +### stderr +${result.stderr} +`.trimStart() + ); + } +} beforeAll(() => { createTestConfigDir(); }); function createTestConfigDir() { - // Clear the test config dir. rimraf.sync(TEST_CONFIG_DIR); fs.mkdirSync(TEST_CONFIG_DIR); - // Copy all rule files into the test config dir. - ruleFiles.forEach((ruleFileName) => { - const config = require(`../${ruleFileName}`); + // Change all rules to "warn", so that ESLint warns about unknown rules. + const newRules = Object.keys(config.rules).reduce((obj, ruleName) => { + obj[ruleName] = "warn"; + return obj; + }, {}); - // Change all rules to "warn", so that ESLint warns about unknown rules. - const newRules = Object.keys(config.rules).reduce((obj, ruleName) => { - obj[ruleName] = "warn"; - return obj; - }, {}); + const newConfig = { ...config, rules: newRules }; - const newConfig = { ...config, rules: newRules }; + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, "index.js"), + `module.exports = ${JSON.stringify(newConfig, null, 2)};` + ); - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, ruleFileName), - `module.exports = ${JSON.stringify(newConfig, null, 2)};` - ); - }); + fs.copyFileSync( + path.join(ROOT, "prettier.js"), + path.join(TEST_CONFIG_DIR, "prettier.js") + ); - // Copy the ESLint configs into the test config dir. - configFiles.forEach((configFileName) => { - const config = require(`../${configFileName}`); - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, configFileName), - `module.exports = ${JSON.stringify(config, null, 2)};` - ); - }); + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, ".eslintrc.js"), + `module.exports = ${JSON.stringify(eslintConfig, null, 2)};` + ); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, ".eslintrc.base.js"), + `module.exports = ${JSON.stringify(eslintConfigBase, null, 2)};` + ); } -describe("all rule files have tests in test-lint/", () => { - ruleFiles.forEach((ruleFileName) => { - test(ruleFileName, () => { +describe("all plugins have tests in test-lint/", () => { + plugins.forEach((plugin) => { + test(plugin, () => { const testFileName = - ruleFileName === "vue.js" + plugin === "vue" ? "vue.vue" - : ruleFileName === "@typescript-eslint.js" - ? "@typescript-eslint.ts" - : ruleFileName; + : plugin === "@typescript-eslint" + ? `${plugin}.ts` + : `${plugin}.js`; expect(fs.existsSync(path.join(ROOT, "test-lint", testFileName))).toBe( true ); @@ -70,40 +91,23 @@ describe("all rule files have tests in test-lint/", () => { }); }); -describe("all rule files are included in the ESLint config", () => { - ruleFiles.forEach((ruleFileName) => { - test(ruleFileName, () => { - const name = ruleFileName.replace(/\.js$/, ""); - expect(eslintConfig.extends).toContain(`./${ruleFileName}`); - if (ruleFileName !== "index.js") { - expect(eslintConfigBase.plugins).toContain(name); - } - }); - }); -}); - -describe("all plugin rule files are mentioned in the README", () => { +describe("all plugins are mentioned in the README", () => { const readme = fs.readFileSync(path.join(ROOT, "README.md"), "utf8"); - ruleFiles - .filter((ruleFileName) => ruleFileName !== "index.js") - .forEach((ruleFileName) => { - test(ruleFileName, () => { - const name = ruleFileName.replace(/\.js$/, ""); + plugins + .filter((plugin) => plugin !== "core") + .forEach((plugin) => { + test(plugin, () => { expect(readme).toMatch( - name.startsWith("@") ? name : `eslint-plugin-${name}` + plugin.startsWith("@") ? plugin : `eslint-plugin-${plugin}` ); - expect(readme).toMatch(`"prettier/${name}"`); }); }); }); describe("all special rules are mentioned in the README", () => { const readme = fs.readFileSync(path.join(ROOT, "README.md"), "utf8"); - const specialRuleNames = [].concat( - ...ruleFiles.map((ruleFileName) => { - const rules = require(`../${ruleFileName}`).rules; - return Object.keys(rules).filter((name) => rules[name] === 0); - }) + const specialRuleNames = Object.keys(config.rules).filter( + (name) => config.rules[name] === 0 ); specialRuleNames.forEach((name) => { @@ -114,12 +118,7 @@ describe("all special rules are mentioned in the README", () => { }); describe('all rules are set to "off" or 0', () => { - const allRules = Object.assign( - Object.create(null), - ...ruleFiles.map((ruleFileName) => require(`../${ruleFileName}`).rules) - ); - - Object.entries(allRules).forEach(([name, value]) => { + Object.entries(config.rules).forEach(([name, value]) => { test(name, () => { expect(value === "off" || value === 0).toBe(true); }); @@ -132,7 +131,7 @@ test("there are no unknown rules", () => { ["run", "test:lint-rules", "--silent"], { encoding: "utf8", shell: true } ); - const output = JSON.parse(result.stdout); + const output = parseJson(result); output[0].messages.forEach((message) => { expect(message.message).not.toMatch(/rule\s+'[^']+'.*not found/); diff --git a/unicorn.js b/unicorn.js deleted file mode 100644 index da0d9d8..0000000 --- a/unicorn.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -module.exports = { - rules: { - "unicorn/empty-brace-spaces": "off", - "unicorn/no-nested-ternary": "off", - "unicorn/number-literal-case": "off", - }, -}; diff --git a/vue.js b/vue.js deleted file mode 100644 index d1b0b67..0000000 --- a/vue.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; - -module.exports = { - rules: { - "vue/html-self-closing": 0, - "vue/max-len": 0, - - "vue/array-bracket-newline": "off", - "vue/array-bracket-spacing": "off", - "vue/arrow-spacing": "off", - "vue/block-spacing": "off", - "vue/block-tag-newline": "off", - "vue/brace-style": "off", - "vue/comma-dangle": "off", - "vue/comma-spacing": "off", - "vue/comma-style": "off", - "vue/dot-location": "off", - "vue/func-call-spacing": "off", - "vue/html-closing-bracket-newline": "off", - "vue/html-closing-bracket-spacing": "off", - "vue/html-end-tags": "off", - "vue/html-indent": "off", - "vue/html-quotes": "off", - "vue/key-spacing": "off", - "vue/keyword-spacing": "off", - "vue/max-attributes-per-line": "off", - "vue/multiline-html-element-content-newline": "off", - "vue/mustache-interpolation-spacing": "off", - "vue/no-extra-parens": "off", - "vue/no-multi-spaces": "off", - "vue/no-spaces-around-equal-signs-in-attribute": "off", - "vue/object-curly-newline": "off", - "vue/object-curly-spacing": "off", - "vue/object-property-newline": "off", - "vue/operator-linebreak": "off", - "vue/script-indent": "off", - "vue/singleline-html-element-content-newline": "off", - "vue/space-in-parens": "off", - "vue/space-infix-ops": "off", - "vue/space-unary-ops": "off", - "vue/template-curly-spacing": "off", - }, -}; From ce106325f963bbd688f6168003f24592624dbd33 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 21 Feb 2021 11:02:12 +0100 Subject: [PATCH 2/4] Fix missing file in build --- scripts/build.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/build.js b/scripts/build.js index 27990d4..44e6970 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -17,6 +17,7 @@ const FILES_TO_COPY = [ transform: (content) => content.replace(/^---[^]*/m, READ_MORE), }, { src: "index.js" }, + { src: "prettier.js" }, ...fs .readdirSync(path.join(DIR, "bin")) .filter((file) => !file.startsWith(".") && file.endsWith(".js")) From 064ebee6afb97ad22df561360b1cc4b64820463d Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 21 Feb 2021 11:07:37 +0100 Subject: [PATCH 3/4] Tweak readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 801b20b..c35d074 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ That’s it! Extending `"prettier"` turns off a bunch of core ESLint rules, as w - [eslint-plugin-unicorn] - [eslint-plugin-vue] -> Note: You might find guides on the Internet that say you should extend stuff like `"prettier/react"`. Since v8.0.0 of eslint-config-prettier, all you need is to extend `"prettier"`! +> Note: You might find guides on the Internet saying you should also extend stuff like `"prettier/react"`. Since version 8.0.0 of eslint-config-prettier, all you need to extend is `"prettier"`! That includes all plugins. ### Excluding deprecated rules From c724ed8efa8ab53aac0475ec0d6a3bd78c7f3146 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 21 Feb 2021 11:36:55 +0100 Subject: [PATCH 4/4] Final readme tweak --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c35d074..5dd8d9d 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ For maximum ease of use, the special rules are disabled by default (provided tha **These rules might cause problems if using [eslint-plugin-prettier] and `--fix`.** -See [`arrow-body-style` and `prefer-arrow-callback` issue][eslint-plugin-prettier-autofix-issue] for details. +See the [`arrow-body-style` and `prefer-arrow-callback` issue][eslint-plugin-prettier-autofix-issue] for details. There are a couple of ways to turn these rules off: @@ -138,6 +138,8 @@ There are a couple of ways to turn these rules off: - Use [eslint-plugin-prettier’s recommended config][eslint-plugin-prettier-recommended], which also turns off these two rules. - Remove them from your config or turn them off manually. +It doesn’t matter which approach you use – they’re all the same. + Note: The CLI tool only reports these as problematic if the `"prettier/prettier"` _rule_ is enabled for the same file. These rules are safe to use if you don’t use [eslint-plugin-prettier]. In other words, if you run `eslint --fix` and `prettier --write` as separate steps.