diff --git a/.eslintignore b/.eslintignore index 0f8b6ec..3f593a7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ !/.* +/build/ /test-config/ diff --git a/.eslintrc.base.js b/.eslintrc.base.js index 3e43b40..b4d1e13 100644 --- a/.eslintrc.base.js +++ b/.eslintrc.base.js @@ -1,6 +1,9 @@ "use strict"; -const pkg = require("./package.json"); +// 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"); module.exports = { extends: [ @@ -12,8 +15,12 @@ module.exports = { ], plugins: [ "prettier", - ...pkg.files - .filter((name) => !name.includes("/") && name !== "index.js") + ...fs + .readdirSync(__dirname) + .filter( + (file) => + !file.startsWith(".") && file.endsWith(".js") && file !== "index.js" + ) .map((ruleFileName) => ruleFileName.replace(/\.js$/, "")), ], parserOptions: { @@ -37,7 +44,9 @@ module.exports = { strict: "error", "prefer-spread": "off", "require-jsdoc": "off", - "prettier/prettier": ["error", {}], + "prettier/prettier": "error", + // Force a conflict with eslint-plugin-prettier in test-lint/prettier.js. + "prefer-arrow-callback": "error", // Force a conflict with Prettier in test-lint/flowtype.js. "flowtype/object-type-delimiter": ["error", "semicolon"], "react/jsx-filename-extension": "off", diff --git a/.eslintrc.js b/.eslintrc.js index 63b3059..83181a0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,12 +1,19 @@ "use strict"; -const pkg = require("./package.json"); +// This is the internal ESLint config for this project itself – it’s not part of +// the eslint-config-prettier npm package. The idea here is to extends some +// sharable config from npm and then include the configs exposed by this package +// 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", - ...pkg.files - .filter((name) => !name.includes("/")) + ...fs + .readdirSync(__dirname) + .filter((file) => !file.startsWith(".") && file.endsWith(".js")) .map((ruleFileName) => `./${ruleFileName}`), ], rules: { diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 47f3602..3bacd41 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -38,3 +38,6 @@ jobs: - name: Prettier run: npx --no-install prettier --check . + + - name: Build + run: npm run build diff --git a/.gitignore b/.gitignore index 9e4b161..fbad133 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +/build/ /node_modules/ /test-config/ diff --git a/.prettierignore b/.prettierignore index 2f50b84..d61e61b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ +/build/ /test-config/ .vscode diff --git a/README.md b/README.md index 348c775..b3c5fd1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This lets you use your favorite shareable config without letting its stylistic c Note that this config _only_ turns rules _off,_ so it only makes sense using it together with some other config. -## Contents +--- @@ -14,6 +14,7 @@ Note that this config _only_ turns rules _off,_ so it only makes sense using it - [Installation](#installation) - [Excluding deprecated rules](#excluding-deprecated-rules) - [CLI helper tool](#cli-helper-tool) + - [Legacy](#legacy) - [Special rules](#special-rules) - [arrow-body-style and prefer-arrow-callback](#arrow-body-style-and-prefer-arrow-callback) - [curly](#curly) @@ -61,6 +62,7 @@ A few ESLint plugins are supported as well: - [@typescript-eslint/eslint-plugin] - [eslint-plugin-babel] - [eslint-plugin-flowtype] +- [eslint-plugin-prettier] - [eslint-plugin-react] - [eslint-plugin-standard] - [eslint-plugin-unicorn] @@ -77,6 +79,7 @@ Add extra exclusions for the plugins you use like so: "prettier/@typescript-eslint", "prettier/babel", "prettier/flowtype", + "prettier/prettier", "prettier/react", "prettier/standard", "prettier/unicorn", @@ -115,17 +118,15 @@ eslint-config-prettier also ships with a little CLI tool to help you check if yo You can run it using `npx`: ``` -npx eslint --print-config path/to/main.js | npx eslint-config-prettier-check +npx eslint-config-prettier path/to/main.js ``` (Change `path/to/main.js` to a file that exists in your project.) -In theory you need to run `npx eslint --print-config file.js | npx eslint-config-prettier-check` for every single file in your project to be 100% sure that there are no conflicting rules, because ESLint supports having different rules for different files. But usually you’ll have about the same rules for all files, so it is enough to run the command on one file (pick one that you won’t be moving). If you use [multiple configuration files] or [overrides], you can (but you probably don’t need to!) run the above script several times with different `--print-config` arguments, such as: +In theory you need to run the tool for every single file in your project to be 100% sure that there are no conflicting rules, because ESLint supports having different rules for different files. But usually you’ll have about the same rules for all files, so it is good enough to run the command on one file. But if you use [multiple configuration files] or [overrides], you can provide several files check: ``` -npx eslint --print-config index.js | npx eslint-config-prettier-check -npx eslint --print-config test/index.js | npx eslint-config-prettier-check -npx eslint --print-config legacy/main.js | npx eslint-config-prettier-check +npx eslint-config-prettier index.js test/index.js legacy/main.js ``` Exit codes: @@ -134,6 +135,20 @@ Exit codes: - 1: Unexpected error. - 2: Conflicting rules found. +### Legacy + +eslint-config-prettier versions before 7.0.0 had a slightly different CLI tool that was run in a different way. For example: + +``` +npx eslint --print-config index.js | npx eslint-config-prettier-check +``` + +If you find something like that in a tutorial, this is what the command looks like in 7.0.0 or later: + +``` +npx eslint-config-prettier index.js +``` + ## Special rules There a few rules that eslint-config-prettier disables that actually can be enabled in some cases. @@ -142,30 +157,23 @@ There a few rules that eslint-config-prettier disables that actually can be enab - Some require special attention when writing code. The CLI helper tool warns you if any of those rules are enabled, but can’t tell if anything is problematic. - Some can cause problems if using [eslint-plugin-prettier] and `--fix`. -For maximum ease of use, the special rules are disabled by default. If you want them, you need to explicitly specify them in your ESLint config. +For maximum ease of use, the special rules are disabled by default (provided that you include all needed things in `"extends"`). If you want them, you need to explicitly specify them in your ESLint config. ### [arrow-body-style] and [prefer-arrow-callback] **These rules might cause problems if using [eslint-plugin-prettier] and `--fix`.** -If you use any of these rules together with the `prettier/prettier` rule from [eslint-plugin-prettier], you can in some cases end up with invalid code due to a bug in ESLint’s autofix. - -These rules are safe to use if: - -- You don’t use [eslint-plugin-prettier]. In other words, you run `eslint --fix` and `prettier --write` as separate steps. -- You _do_ use [eslint-plugin-prettier], but don’t use `--fix`. (But then, what’s the point?) - -You _can_ still use these rules together with [eslint-plugin-prettier] if you want, because the bug does not occur _all the time._ But if you do, you need to keep in mind that you might end up with invalid code, where you manually have to insert a missing closing parenthesis to get going again. +See [`arrow-body-style` and `prefer-arrow-callback` issue][eslint-plugin-prettier-autofix-issue] for details. -If you’re fixing large of amounts of previously unformatted code, consider temporarily disabling the `prettier/prettier` rule and running `eslint --fix` and `prettier --write` separately. +There are a couple of ways to turn these rules off: -See these issues for more information: +- Put `"prettier/prettier"` in your `"extends"`. (Yes, there’s both a _rule_ called `"prettier/prettier"` and a _config_ called `"prettier/prettier"`.) +- 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. -- [eslint-config-prettier#31] -- [eslint-config-prettier#71] -- [eslint-plugin-prettier#65] +Note: The CLI tool only reports these as problematic if the `"prettier/prettier"` _rule_ is enabled for the same file. -When the autofix bug in ESLint has been fixed, the special case for these rules can be removed. +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. ### [curly] @@ -388,31 +396,13 @@ Example ESLint configuration: ### [no-tabs] -**This rule requires certain Prettier options.** - -This rule disallows the use of tab characters at all. It can be used just fine with Prettier as long as you don’t configure Prettier to indent using tabs. - -Example ESLint configuration: - - -```json -{ - "rules": { - "no-tabs": "error" - } -} -``` +**This rule requires certain options.** -Example Prettier configuration (this is the default, so adding this is not required): +This rule disallows the use of tab characters. By default the rule forbids _all_ tab characters. That can be used just fine with Prettier as long as you don’t configure Prettier to indent using tabs. - -```json -{ - "useTabs": false -} -``` +Luckily, it’s possible to configure the rule so that it works regardless of whether Prettier uses spaces or tabs: Set `allowIndentationTabs` to `true`. This way Prettier takes care of your indentation, while the `no-tabs` takes care of potential tab characters anywhere else in your code. -**Note:** Since [ESlint 5.7.0] this rule can be configured to work regardless of your Prettier configuration: +Example ESLint configuration: ```json @@ -423,8 +413,6 @@ Example Prettier configuration (this is the default, so adding this is not requi } ``` -A future version of eslint-config-prettier might check for that automatically. - ### [no-unexpected-multiline] **This rule requires special attention when writing code.** @@ -691,6 +679,7 @@ You can also supply a custom message if you want: eslint-config-prettier has been tested with: - ESLint 7.14.0 + - eslint-config-prettier 7.0.0 requires ESLint 7.0.0 or newer, while eslint-config-prettier 6.15.0 and older should also work with ESLint versions down to 3.x. - eslint-config-prettier 6.11.0 and older were tested with ESLint 6.x - eslint-config-prettier 5.1.0 and older were tested with ESLint 5.x - eslint-config-prettier 2.10.0 and older were tested with ESLint 4.x @@ -699,6 +688,7 @@ eslint-config-prettier has been tested with: - @typescript-eslint/eslint-plugin 4.8.2 - eslint-plugin-babel 5.3.1 - eslint-plugin-flowtype 5.2.0 +- eslint-plugin-prettier 3.1.4 - eslint-plugin-react 7.21.5 - eslint-plugin-standard 4.0.2 - eslint-plugin-unicorn 23.0.0 @@ -737,10 +727,9 @@ console.log(); Finally, you need to mention the plugin in several places: -- Add `"foobar.js"` to the "files" field in `package.json`. - 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, to the example config and to Contributing section in `README.md`. +- Add it to the list of supported plugins and to the Contributing section in `README.md`. When you’re done, run `npm test` to verify that you got it all right. It runs several other npm scripts: @@ -759,17 +748,14 @@ When you’re done, run `npm test` to verify that you got it all right. It runs [@typescript-eslint/eslint-plugin]: https://github.com/typescript-eslint/typescript-eslint [@typescript-eslint/quotes]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/quotes.md -[eslint 5.7.0]: https://eslint.org/blog/2018/10/eslint-v5.7.0-released -[prettier]: https://github.com/prettier/prettier [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-config-prettier#31]: https://github.com/prettier/eslint-config-prettier/issues/31 -[eslint-config-prettier#71]: https://github.com/prettier/eslint-config-prettier/issues/71 [eslint-plugin-babel]: https://github.com/babel/eslint-plugin-babel [eslint-plugin-flowtype]: https://github.com/gajus/eslint-plugin-flowtype -[eslint-plugin-prettier#65]: https://github.com/prettier/eslint-plugin-prettier/issues/65 +[eslint-plugin-prettier-autofix-issue]: https://github.com/prettier/eslint-plugin-prettier#arrow-body-style-and-prefer-arrow-callback-issue +[eslint-plugin-prettier-recommended]: https://github.com/prettier/eslint-plugin-prettier#recommended-configuration [eslint-plugin-prettier]: https://github.com/prettier/eslint-plugin-prettier [eslint-plugin-react]: https://github.com/yannickcr/eslint-plugin-react [eslint-plugin-standard]: https://github.com/xjamundx/eslint-plugin-standard @@ -787,6 +773,7 @@ When you’re done, run `npm test` to verify that you got it all right. It runs [no-unexpected-multiline]: https://eslint.org/docs/rules/no-unexpected-multiline [overrides]: https://eslint.org/docs/user-guide/configuring#configuration-based-on-glob-patterns [prefer-arrow-callback]: https://eslint.org/docs/rules/prefer-arrow-callback +[prettier]: https://github.com/prettier/prettier [quotes]: https://eslint.org/docs/rules/quotes [singlequote]: https://prettier.io/docs/en/options.html#quotes [string formatting rules]: https://prettier.io/docs/en/rationale.html#strings diff --git a/bin/cli.js b/bin/cli.js index 208b143..8d5d74e 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -4,38 +4,35 @@ const fs = require("fs"); const path = require("path"); -const getStdin = require("get-stdin"); const validators = require("./validators"); +// Require locally installed eslint, for `npx eslint-config-prettier` support +// with no local eslint-config-prettier installation. +const { ESLint } = require(require.resolve("eslint", { + paths: [process.cwd(), ...require.resolve.paths("eslint")], +})); + const SPECIAL_RULES_URL = "https://github.com/prettier/eslint-config-prettier#special-rules"; if (module === require.main) { - if (process.argv.length > 2 || process.stdin.isTTY) { - console.error( - [ - "This tool checks whether an ESLint configuration contains rules that are", - "unnecessary or conflict with Prettier. It’s supposed to be run like this:", - "", - " npx eslint --print-config path/to/main.js | npx eslint-config-prettier-check", - " npx eslint --print-config test/index.js | npx eslint-config-prettier-check", - "", - "Exit codes:", - "", - "0: No automatically detectable problems found.", - "1: Unexpected error.", - "2: Conflicting rules found.", - "", - "For more information, see:", - "https://github.com/prettier/eslint-config-prettier#cli-helper-tool", - ].join("\n") - ); + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error(help()); process.exit(1); } - getStdin() - .then((string) => { - const result = processString(string); + const eslint = new ESLint(); + + 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]]) + ) + ); + const result = processRules(rules); if (result.stderr) { console.error(result.stderr); } @@ -45,34 +42,32 @@ if (module === require.main) { process.exit(result.code); }) .catch((error) => { - console.error("Unexpected error", error); + console.error(error.message); process.exit(1); }); } -function processString(string) { - let config; - try { - config = JSON.parse(string); - } catch (error) { - return { - stderr: `Failed to parse JSON:\n${error.message}`, - code: 1, - }; - } +function help() { + return ` +Usage: npx eslint-config-prettier FILE... - if ( - !( - Object.prototype.toString.call(config) === "[object Object]" && - Object.prototype.toString.call(config.rules) === "[object Object]" - ) - ) { - return { - stderr: `Expected a \`{"rules: {...}"}\` JSON object, but got:\n${string}`, - code: 1, - }; - } +Resolves an ESLint configuration for every given FILE and checks if they +contain rules that are unnecessary or conflict with Prettier. Example: + + npx eslint-config-prettier index.js test/index.js other/file/to/check.js + +Exit codes: + +0: No automatically detectable problems found. +1: General error. +2: Conflicting rules found. + +For more information, see: +https://github.com/prettier/eslint-config-prettier#cli-helper-tool + `.trim(); +} +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 @@ -84,10 +79,7 @@ function processString(string) { .map((ruleFileName) => require(`../${ruleFileName}`).rules) ); - const regularRules = filterRules( - allRules, - (ruleName, value) => value === "off" - ); + const regularRules = filterRules(allRules, (_, value) => value === "off"); const optionsRules = filterRules( allRules, (ruleName, value) => value === 0 && ruleName in validators @@ -97,29 +89,31 @@ function processString(string) { (ruleName, value) => value === 0 && !(ruleName in validators) ); - const flaggedRules = Object.keys(config.rules) - .map((ruleName) => { - const value = config.rules[ruleName]; + const enabledRules = configRules + .map(([ruleName, value, source]) => { const arrayValue = Array.isArray(value) ? value : [value]; - const level = arrayValue[0]; - const options = arrayValue.slice(1); + const [level, ...options] = arrayValue; const isOff = level === "off" || level === 0; - return !isOff && ruleName in allRules ? { ruleName, options } : null; + return isOff ? null : { ruleName, options, source }; }) .filter(Boolean); + const flaggedRules = enabledRules.filter( + ({ ruleName }) => ruleName in allRules + ); + const regularFlaggedRuleNames = filterRuleNames( flaggedRules, - (ruleName) => ruleName in regularRules + ({ ruleName }) => ruleName in regularRules ); const optionsFlaggedRuleNames = filterRuleNames( flaggedRules, - (ruleName, options) => - ruleName in optionsRules && !validators[ruleName](options) + ({ ruleName, ...rule }) => + ruleName in optionsRules && !validators[ruleName](rule, enabledRules) ); const specialFlaggedRuleNames = filterRuleNames( flaggedRules, - (ruleName) => ruleName in specialRules + ({ ruleName }) => ruleName in specialRules ); if ( @@ -154,7 +148,7 @@ function processString(string) { ].join("\n"); const optionsMessage = [ - "The following rules are enabled with options that might conflict with Prettier. See:", + "The following rules are enabled with config that might conflict with Prettier. See:", SPECIAL_RULES_URL, "", printRuleNames(optionsFlaggedRuleNames), @@ -182,18 +176,18 @@ function processString(string) { } function filterRules(rules, fn) { - return Object.keys(rules) - .filter((ruleName) => fn(ruleName, rules[ruleName])) - .reduce((obj, ruleName) => { + return Object.entries(rules) + .filter(([ruleName, value]) => fn(ruleName, value)) + .reduce((obj, [ruleName]) => { obj[ruleName] = true; return obj; }, Object.create(null)); } function filterRuleNames(rules, fn) { - return rules - .filter((rule) => fn(rule.ruleName, rule.options)) - .map((rule) => rule.ruleName); + return [ + ...new Set(rules.filter((rule) => fn(rule)).map((rule) => rule.ruleName)), + ]; } function printRuleNames(ruleNames) { @@ -204,4 +198,4 @@ function printRuleNames(ruleNames) { .join("\n"); } -exports.processString = processString; +exports.processRules = processRules; diff --git a/bin/validators.js b/bin/validators.js index a6a9b8a..ef3eab4 100644 --- a/bin/validators.js +++ b/bin/validators.js @@ -4,7 +4,9 @@ // `false` if the options DO conflict with Prettier, and `true` if they don’t. module.exports = { - curly(options) { + "arrow-body-style": checkEslintPluginPrettier, + + curly({ options }) { if (options.length === 0) { return true; } @@ -13,7 +15,7 @@ module.exports = { return firstOption !== "multi-line" && firstOption !== "multi-or-nest"; }, - "lines-around-comment"(options) { + "lines-around-comment"({ options }) { if (options.length === 0) { return false; } @@ -30,7 +32,7 @@ module.exports = { ); }, - "no-confusing-arrow"(options) { + "no-confusing-arrow"({ options }) { if (options.length === 0) { return false; } @@ -39,7 +41,18 @@ module.exports = { return firstOption ? firstOption.allowParens === false : false; }, - "vue/html-self-closing"(options) { + "no-tabs"({ options }) { + if (options.length === 0) { + return false; + } + + const firstOption = options[0]; + return Boolean(firstOption && firstOption.allowIndentationTabs); + }, + + "prefer-arrow-callback": checkEslintPluginPrettier, + + "vue/html-self-closing"({ options }) { if (options.length === 0) { return false; } @@ -52,3 +65,10 @@ module.exports = { ); }, }; + +function checkEslintPluginPrettier({ source: currentSource }, enabledRules) { + return !enabledRules.some( + ({ ruleName, source }) => + ruleName === "prettier/prettier" && currentSource === source + ); +} diff --git a/index.js b/index.js index 1cbfd47..a6d9cb1 100644 --- a/index.js +++ b/index.js @@ -3,101 +3,97 @@ const includeDeprecated = !process.env.ESLINT_CONFIG_PRETTIER_NO_DEPRECATED; module.exports = { - rules: Object.assign( - { - // 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.) - "arrow-body-style": 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, - "prefer-arrow-callback": 0, - quotes: 0, - // The rest are rules that you never need to enable when using Prettier. - "array-bracket-newline": "off", - "array-bracket-spacing": "off", - "array-element-newline": "off", - "arrow-parens": "off", - "arrow-spacing": "off", - "block-spacing": "off", - "brace-style": "off", - "comma-dangle": "off", - "comma-spacing": "off", - "comma-style": "off", - "computed-property-spacing": "off", - "dot-location": "off", - "eol-last": "off", - "func-call-spacing": "off", - "function-call-argument-newline": "off", - "function-paren-newline": "off", - "generator-star": "off", - "generator-star-spacing": "off", - "implicit-arrow-linebreak": "off", - indent: "off", - "jsx-quotes": "off", - "key-spacing": "off", - "keyword-spacing": "off", - "linebreak-style": "off", - "multiline-ternary": "off", - "newline-per-chained-call": "off", - "new-parens": "off", - "no-arrow-condition": "off", - "no-comma-dangle": "off", - "no-extra-parens": "off", - "no-extra-semi": "off", - "no-floating-decimal": "off", - "no-mixed-spaces-and-tabs": "off", - "no-multi-spaces": "off", - "no-multiple-empty-lines": "off", - "no-reserved-keys": "off", - "no-space-before-semi": "off", - "no-trailing-spaces": "off", - "no-whitespace-before-property": "off", - "no-wrap-func": "off", - "nonblock-statement-body-position": "off", - "object-curly-newline": "off", - "object-curly-spacing": "off", - "object-property-newline": "off", - "one-var-declaration-per-line": "off", - "operator-linebreak": "off", - "padded-blocks": "off", - "quote-props": "off", - "rest-spread-spacing": "off", - semi: "off", - "semi-spacing": "off", - "semi-style": "off", - "space-after-function-name": "off", - "space-after-keywords": "off", - "space-before-blocks": "off", - "space-before-function-paren": "off", - "space-before-function-parentheses": "off", - "space-before-keywords": "off", - "space-in-brackets": "off", - "space-in-parens": "off", - "space-infix-ops": "off", - "space-return-throw-case": "off", - "space-unary-ops": "off", - "space-unary-word-ops": "off", - "switch-colon-spacing": "off", - "template-curly-spacing": "off", - "template-tag-spacing": "off", - "unicode-bom": "off", - "wrap-iife": "off", - "wrap-regex": "off", - "yield-star-spacing": "off", - }, - includeDeprecated && { + rules: { + // 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, + "lines-around-comment": 0, + "max-len": 0, + "no-confusing-arrow": 0, + "no-mixed-operators": 0, + "no-tabs": 0, + "no-unexpected-multiline": 0, + quotes: 0, + // The rest are rules that you never need to enable when using Prettier. + "array-bracket-newline": "off", + "array-bracket-spacing": "off", + "array-element-newline": "off", + "arrow-parens": "off", + "arrow-spacing": "off", + "block-spacing": "off", + "brace-style": "off", + "comma-dangle": "off", + "comma-spacing": "off", + "comma-style": "off", + "computed-property-spacing": "off", + "dot-location": "off", + "eol-last": "off", + "func-call-spacing": "off", + "function-call-argument-newline": "off", + "function-paren-newline": "off", + "generator-star": "off", + "generator-star-spacing": "off", + "implicit-arrow-linebreak": "off", + indent: "off", + "jsx-quotes": "off", + "key-spacing": "off", + "keyword-spacing": "off", + "linebreak-style": "off", + "multiline-ternary": "off", + "newline-per-chained-call": "off", + "new-parens": "off", + "no-arrow-condition": "off", + "no-comma-dangle": "off", + "no-extra-parens": "off", + "no-extra-semi": "off", + "no-floating-decimal": "off", + "no-mixed-spaces-and-tabs": "off", + "no-multi-spaces": "off", + "no-multiple-empty-lines": "off", + "no-reserved-keys": "off", + "no-space-before-semi": "off", + "no-trailing-spaces": "off", + "no-whitespace-before-property": "off", + "no-wrap-func": "off", + "nonblock-statement-body-position": "off", + "object-curly-newline": "off", + "object-curly-spacing": "off", + "object-property-newline": "off", + "one-var-declaration-per-line": "off", + "operator-linebreak": "off", + "padded-blocks": "off", + "quote-props": "off", + "rest-spread-spacing": "off", + semi: "off", + "semi-spacing": "off", + "semi-style": "off", + "space-after-function-name": "off", + "space-after-keywords": "off", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-before-function-parentheses": "off", + "space-before-keywords": "off", + "space-in-brackets": "off", + "space-in-parens": "off", + "space-infix-ops": "off", + "space-return-throw-case": "off", + "space-unary-ops": "off", + "space-unary-word-ops": "off", + "switch-colon-spacing": "off", + "template-curly-spacing": "off", + "template-tag-spacing": "off", + "unicode-bom": "off", + "wrap-iife": "off", + "wrap-regex": "off", + "yield-star-spacing": "off", + ...(includeDeprecated && { // Deprecated since version 4.0.0. // https://github.com/eslint/eslint/pull/8286 "indent-legacy": "off", // Deprecated since version 3.3.0. // https://eslint.org/docs/rules/no-spaced-func "no-spaced-func": "off", - } - ), + }), + }, }; diff --git a/package-lock.json b/package-lock.json index 6a60ed0..3282e06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,6 @@ { - "name": "eslint-config-prettier", - "version": "6.15.0", - "lockfileVersion": 1, "requires": true, + "lockfileVersion": 1, "dependencies": { "@babel/code-frame": { "version": "7.10.4", @@ -3592,11 +3590,6 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==" - }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", diff --git a/package-real.json b/package-real.json new file mode 100644 index 0000000..43d346c --- /dev/null +++ b/package-real.json @@ -0,0 +1,13 @@ +{ + "name": "eslint-config-prettier", + "version": "6.15.0", + "license": "MIT", + "author": "Simon Lydell", + "description": "Turns off all rules that are unnecessary or might conflict with Prettier.", + "repository": "prettier/eslint-config-prettier", + "bin": "bin/cli.js", + "keywords": ["eslint", "eslintconfig", "prettier"], + "peerDependencies": { + "eslint": ">=7.0.0" + } +} diff --git a/package.json b/package.json index 45390b8..d2868ad 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,5 @@ { - "name": "eslint-config-prettier", - "version": "6.15.0", - "license": "MIT", - "author": "Simon Lydell", - "description": "Turns off all rules that are unnecessary or might conflict with Prettier.", - "repository": "prettier/eslint-config-prettier", - "files": [ - "bin/", - "@typescript-eslint.js", - "babel.js", - "flowtype.js", - "index.js", - "react.js", - "standard.js", - "unicorn.js", - "vue.js" - ], - "bin": { - "eslint-config-prettier-check": "bin/cli.js" - }, - "keywords": [ - "eslint", - "eslintconfig", - "prettier" - ], + "private": true, "scripts": { "doctoc": "doctoc README.md && replace \"\\[\\[([\\w/-]+)\\](?:([^\\[\\]]+)\\[([\\w/-]+)\\])?\\]\" \"[\\$1\\$2\\$3]\" README.md", "prettier": "prettier --write .", @@ -32,12 +8,10 @@ "test:lint-rules": "eslint index.js --config test-config/.eslintrc.js --format json", "test:deprecated": "eslint-find-rules --deprecated index.js", "test:jest": "jest", - "test:cli-sanity": "eslint --print-config index.js | node ./bin/cli.js", - "test:cli-sanity-warning": "eslint --print-config ./bin/cli.js | node ./bin/cli.js", - "test": "npm run test:lint && npm run test:jest && npm run test:cli-sanity && npm run test:cli-sanity-warning" - }, - "dependencies": { - "get-stdin": "^6.0.0" + "test:cli-sanity": "node ./bin/cli.js index.js", + "test:cli-sanity-warning": "node ./bin/cli.js react.js ./bin/cli.js", + "test": "npm run test:lint && npm run test:jest && npm run test:cli-sanity && npm run test:cli-sanity-warning && npm run build", + "build": "node scripts/build.js" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "4.8.2", @@ -59,8 +33,5 @@ "replace": "1.2.0", "rimraf": "3.0.2", "typescript": "4.1.2" - }, - "peerDependencies": { - "eslint": ">=3.14.1" } } diff --git a/prettier.js b/prettier.js new file mode 100644 index 0000000..aac873c --- /dev/null +++ b/prettier.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + rules: { + // These are safe to use as long as the `"prettier/prettier"` rule from + // eslint-plugin-prettier isn’t enabled. + // These are also included in `"plugin:prettier/recommended"`: + // https://github.com/prettier/eslint-plugin-prettier#recommended-configuration + "arrow-body-style": 0, + "prefer-arrow-callback": 0, + }, +}; diff --git a/react.js b/react.js index 3f48553..5d79ba9 100644 --- a/react.js +++ b/react.js @@ -3,27 +3,25 @@ const includeDeprecated = !process.env.ESLINT_CONFIG_PRETTIER_NO_DEPRECATED; module.exports = { - rules: Object.assign( - { - "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-one-expression-per-line": "off", - "react/jsx-props-no-multi-spaces": "off", - "react/jsx-tag-spacing": "off", - "react/jsx-wrap-multilines": "off", - }, - includeDeprecated && { + 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-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 new file mode 100644 index 0000000..8642d8c --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,45 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +const DIR = path.join(__dirname, ".."); +const BUILD = path.join(DIR, "build"); + +const READ_MORE = + "**[➡️ Full readme](https://github.com/prettier/eslint-config-prettier/)**"; + +const FILES_TO_COPY = [ + { src: "LICENSE" }, + { src: "package-real.json", dest: "package.json" }, + { + src: "README.md", + transform: (content) => content.replace(/^---[^]*/m, READ_MORE), + }, + ...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)) { + fs.rmdirSync(BUILD, { recursive: true }); +} + +fs.mkdirSync(BUILD); + +for (const { src, dest = src, transform } of FILES_TO_COPY) { + if (transform) { + fs.writeFileSync( + path.join(BUILD, dest), + transform(fs.readFileSync(path.join(DIR, src), "utf8")) + ); + } else { + fs.mkdirSync(path.dirname(path.join(BUILD, dest)), { recursive: true }); + fs.copyFileSync(path.join(DIR, src), path.join(BUILD, dest)); + } +} diff --git a/test-lint/prettier.js b/test-lint/prettier.js new file mode 100644 index 0000000..1a28a90 --- /dev/null +++ b/test-lint/prettier.js @@ -0,0 +1,14 @@ +/* eslint-disable quotes */ +/* eslint-disable space-before-function-paren */ +"use strict"; + +function foo() { + return ( + isTrue && + [0, 1, 2].map(function (num) { + return num * 2; + }) + ); +} + +foo(); diff --git a/test/cli.test.js b/test/cli.test.js index a5ba260..1b6d888 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -7,16 +7,13 @@ const onPatterns = [1, 2, "warn", "error", [1], [2], ["warn"], ["error"]]; function createRules(rules, pattern) { const arrayPattern = Array.isArray(pattern) ? pattern : [pattern]; - const rulesString = rules - .map((rule) => { - const value = Array.isArray(rule) - ? arrayPattern.concat(rule.slice(1)) - : pattern; - const name = Array.isArray(rule) ? rule[0] : rule; - return `"${name}": ${JSON.stringify(value)}`; - }) - .join(", "); - return `{"rules": {${rulesString}}}`; + return rules.map((rule) => { + const value = Array.isArray(rule) + ? arrayPattern.concat(rule.slice(1)) + : pattern; + const name = Array.isArray(rule) ? rule[0] : rule; + return [name, value, "test-source.js"]; + }); } describe("does not flag", () => { @@ -24,16 +21,16 @@ describe("does not flag", () => { const results = offPatterns.map((pattern) => ({ pattern: JSON.stringify(pattern), - result: cli.processString(createRules(rules, pattern)), + result: cli.processRules(createRules(rules, pattern)), })); test("result", () => { expect(results[0].result).toMatchInlineSnapshot(` -Object { - "code": 0, - "stdout": "No rules that are unnecessary or conflict with Prettier were found.", -} -`); + Object { + "code": 0, + "stdout": "No rules that are unnecessary or conflict with Prettier were found.", + } + `); }); results.forEach(({ pattern, result }) => { @@ -49,20 +46,20 @@ describe("does flag", () => { const results = onPatterns.map( (pattern) => ({ pattern: JSON.stringify(pattern), - result: cli.processString(createRules(rules, pattern)), + result: cli.processRules(createRules(rules, pattern)), }), {} ); test("result", () => { expect(results[0].result).toMatchInlineSnapshot(` -Object { - "code": 2, - "stdout": "The following rules are unnecessary or might conflict with Prettier: + Object { + "code": 2, + "stdout": "The following rules are unnecessary or might conflict with Prettier: -- arrow-parens", -} -`); + - arrow-parens", + } + `); }); results.forEach(({ pattern, result }) => { @@ -74,40 +71,40 @@ Object { test("no results", () => { const rules = ["strict", "curly"]; - expect(cli.processString(createRules(rules, "error"))).toMatchInlineSnapshot(` -Object { - "code": 0, - "stdout": "No rules that are unnecessary or conflict with Prettier were found.", -} -`); + expect(cli.processRules(createRules(rules, "error"))).toMatchInlineSnapshot(` + Object { + "code": 0, + "stdout": "No rules that are unnecessary or conflict with Prettier were found.", + } + `); }); test("conflicting options", () => { const rules = ["strict", ["curly", "multi-line"]]; - expect(cli.processString(createRules(rules, "error"))).toMatchInlineSnapshot(` -Object { - "code": 2, - "stdout": "The following rules are enabled with options that might conflict with Prettier. See: -https://github.com/prettier/eslint-config-prettier#special-rules - -- curly", -} -`); + expect(cli.processRules(createRules(rules, "error"))).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 + + - curly", + } + `); }); test("special rules", () => { const rules = ["strict", "max-len"]; - expect(cli.processString(createRules(rules, "error"))).toMatchInlineSnapshot(` -Object { - "code": 0, - "stdout": "No rules that are unnecessary or conflict with Prettier were found. + 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: -https://github.com/prettier/eslint-config-prettier#special-rules + However, the following rules are enabled but cannot be automatically checked. See: + https://github.com/prettier/eslint-config-prettier#special-rules -- max-len", -} -`); + - max-len", + } + `); }); test("all the things", () => { @@ -115,12 +112,16 @@ test("all the things", () => { "strict", "max-len", "arrow-spacing", + "arrow-spacing", "quotes", + ["quotes", "double"], "arrow-parens", "no-tabs", "lines-around-comment", "no-unexpected-multiline", "no-mixed-operators", + "curly", + ["curly", "multi-line"], ["curly", "multi-or-nest", "consistent"], ["no-confusing-arrow", { allowParens: true }], "react/jsx-indent", @@ -129,101 +130,81 @@ test("all the things", () => { "prefer-arrow-callback", "arrow-body-style", ]; - expect(cli.processString(createRules(rules, "error"))).toMatchInlineSnapshot(` -Object { - "code": 2, - "stdout": "The following rules are unnecessary or might conflict with Prettier: - -- arrow-parens -- arrow-spacing -- flowtype/semi -- react/jsx-indent - -The following rules are enabled with options that might conflict with Prettier. See: -https://github.com/prettier/eslint-config-prettier#special-rules - -- curly -- lines-around-comment -- no-confusing-arrow -- vue/html-self-closing - -The following rules are enabled but cannot be automatically checked. See: -https://github.com/prettier/eslint-config-prettier#special-rules - -- arrow-body-style -- max-len -- no-mixed-operators -- no-tabs -- no-unexpected-multiline -- prefer-arrow-callback -- quotes", -} -`); + expect(cli.processRules(createRules(rules, "error"))).toMatchInlineSnapshot(` + Object { + "code": 2, + "stdout": "The following rules are unnecessary or might conflict with Prettier: + + - arrow-parens + - arrow-spacing + - flowtype/semi + - react/jsx-indent + + The following rules are enabled with config that might conflict with Prettier. See: + https://github.com/prettier/eslint-config-prettier#special-rules + + - curly + - lines-around-comment + - no-confusing-arrow + - no-tabs + - vue/html-self-closing + + The following rules are enabled but cannot be automatically checked. See: + https://github.com/prettier/eslint-config-prettier#special-rules + + - max-len + - no-mixed-operators + - no-unexpected-multiline + - quotes", + } + `); }); -test("invalid JSON", () => { - expect(cli.processString("a")).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Failed to parse JSON: -Unexpected token a in JSON at position 0", -} -`); - expect(cli.processString('{"rules": {')).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Failed to parse JSON: -Unexpected end of JSON input", -} -`); +test("eslint-plugin-prettier", () => { + expect( + cli.processRules([ + ["prettier/prettier", "error", "test-source.js"], + ["arrow-body-style", "error", "test-source.js"], + ["prefer-arrow-callback", "error", "test-source.js"], + ]) + ).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 + + - arrow-body-style + - prefer-arrow-callback", + } + `); }); -test("invalid config", () => { - expect(cli.processString("null")).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Expected a \`{\\"rules: {...}\\"}\` JSON object, but got: -null", -} -`); - - expect(cli.processString("true")).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Expected a \`{\\"rules: {...}\\"}\` JSON object, but got: -true", -} -`); - - expect(cli.processString("false")).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Expected a \`{\\"rules: {...}\\"}\` JSON object, but got: -false", -} -`); - - expect(cli.processString("1")).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Expected a \`{\\"rules: {...}\\"}\` JSON object, but got: -1", -} -`); - - expect(cli.processString('"string"')).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Expected a \`{\\"rules: {...}\\"}\` JSON object, but got: -\\"string\\"", -} -`); +test("eslint-plugin-prettier no warnings because different sources", () => { + expect( + cli.processRules([ + ["prettier/prettier", "error", "test-source.js"], + ["arrow-body-style", "error", "other.js"], + ["prefer-arrow-callback", "error", "other.js"], + ]) + ).toMatchInlineSnapshot(` + Object { + "code": 0, + "stdout": "No rules that are unnecessary or conflict with Prettier were found.", + } + `); +}); - expect(cli.processString("[1, true]")).toMatchInlineSnapshot(` -Object { - "code": 1, - "stderr": "Expected a \`{\\"rules: {...}\\"}\` JSON object, but got: -[1, true]", -} -`); +test("eslint-plugin-prettier no warnings because the rule is off", () => { + expect( + cli.processRules([ + ["prettier/prettier", [0, {}], "test-source.js"], + ["arrow-body-style", "error", "test-source.js"], + ["prefer-arrow-callback", "error", "test-source.js"], + ]) + ).toMatchInlineSnapshot(` + Object { + "code": 0, + "stdout": "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 c983586..4e0c5c8 100644 --- a/test/lint-verify-fail.test.js +++ b/test/lint-verify-fail.test.js @@ -31,7 +31,10 @@ describe("test-lint/ causes errors without eslint-config-prettier", () => { }); test("must only cause errors related to itself", () => { - if (name === "index") { + // ESLint core rules have no prefix. + // eslint-plugin-prettier provides no conflicting rules, but makes two + // core rules unusable. + if (name === "index" || name === "prettier") { expect( ruleIds .filter((ruleId) => ruleId.includes("/")) diff --git a/test/rules.test.js b/test/rules.test.js index 329a8e0..8632acd 100644 --- a/test/rules.test.js +++ b/test/rules.test.js @@ -4,7 +4,6 @@ const childProcess = require("child_process"); const fs = require("fs"); const path = require("path"); const rimraf = require("rimraf"); -const pkg = require("../package.json"); const eslintConfig = require("../.eslintrc"); const eslintConfigBase = require("../.eslintrc.base"); @@ -37,7 +36,7 @@ function createTestConfigDir() { return obj; }, {}); - const newConfig = Object.assign({}, config, { rules: newRules }); + const newConfig = { ...config, rules: newRules }; fs.writeFileSync( path.join(TEST_CONFIG_DIR, ruleFileName), @@ -55,14 +54,6 @@ function createTestConfigDir() { }); } -describe("all rule files are listed in package.json", () => { - ruleFiles.forEach((ruleFileName) => { - test(ruleFileName, () => { - expect(pkg.files).toContain(ruleFileName); - }); - }); -}); - describe("all rule files have tests in test-lint/", () => { ruleFiles.forEach((ruleFileName) => { test(ruleFileName, () => { @@ -128,9 +119,7 @@ describe('all rules are set to "off" or 0', () => { ...ruleFiles.map((ruleFileName) => require(`../${ruleFileName}`).rules) ); - Object.keys(allRules).forEach((name) => { - const value = allRules[name]; - + Object.entries(allRules).forEach(([name, value]) => { test(name, () => { expect(value === "off" || value === 0).toBe(true); }); diff --git a/test/validators.test.js b/test/validators.test.js index 9f96e8d..71a6feb 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -4,11 +4,11 @@ const validators = require("../bin/validators"); const { inspect } = require("util"); expect.extend({ - toPass(validator, opts) { - const pass = validator(opts); + toPass(validator, options) { + const pass = validator({ options, source: "test-source.js" }, []); return { message: () => - `expected ${inspect(opts)} to be ${pass ? "invalid" : "valid"}`, + `expected ${inspect(options)} to be ${pass ? "invalid" : "valid"}`, pass, }; }, @@ -17,8 +17,8 @@ expect.extend({ function rule(name, { valid, invalid }) { test(name, () => { const validator = validators[name]; - valid.forEach((opts) => expect(validator).toPass(opts)); - invalid.forEach((opts) => expect(validator).not.toPass(opts)); + valid.forEach((options) => expect(validator).toPass(options)); + invalid.forEach((options) => expect(validator).not.toPass(options)); }); } @@ -60,6 +60,11 @@ rule("no-confusing-arrow", { invalid: [[], [null], [{ allowParens: true }], [{ other: true }]], }); +rule("no-tabs", { + valid: [[{ allowIndentationTabs: true }]], + invalid: [[], [null], [{ allowIndentationTabs: false }], [{ other: true }]], +}); + rule("vue/html-self-closing", { valid: [ [{ html: { void: "any" } }],