diff --git a/lib/options-manager.js b/lib/options-manager.js index e0ca8dfa..f0d87ef6 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -137,7 +137,21 @@ const normalizeSpaces = opts => { return typeof opts.space === 'number' ? opts.space : 2; }; -const mergeWithPrettierConf = opts => { +const mergeWithPrettierConf = (opts, prettierOpts) => { + if ((opts.semicolon === true && prettierOpts.semi === false) || + (opts.semicolon === false && prettierOpts.semi === true)) { + throw new Error(`The Prettier config \`semi\` is ${prettierOpts.semi} while XO \`semicolon\` is ${opts.semicolon}`); + } + + if (((opts.space === true || typeof opts.space === 'number') && prettierOpts.useTabs === false) || + ((opts.space === false || typeof opts.space === 'number') && prettierOpts.useTabs === true)) { + throw new Error(`The Prettier config \`useTabs\` is ${prettierOpts.useTabs} while XO \`space\` is ${opts.space}`); + } + + if (typeof opts.space === 'number' && typeof prettierOpts.tabWidth === 'number' && opts.space !== prettierOpts.tabWidth) { + throw new Error(`The Prettier config \`tabWidth\` is ${prettierOpts.tabWidth} while XO \`space\` is ${opts.space}`); + } + return mergeWith( {}, { @@ -146,8 +160,8 @@ const mergeWithPrettierConf = opts => { bracketSpacing: false, jsxBracketSameLine: false }, - prettier.resolveConfig.sync(opts.cwd || process.cwd()), {tabWidth: normalizeSpaces(opts), useTabs: !opts.space, semi: opts.semicolon !== false}, + prettierOpts, mergeFn ); }; @@ -193,7 +207,7 @@ const buildConfig = opts => { } } - if (opts.semicolon === false) { + if (opts.semicolon === false && !opts.prettier) { config.rules.semi = ['error', 'never']; config.rules['semi-spacing'] = ['error', { before: false, @@ -257,7 +271,9 @@ const buildConfig = opts => { // The prettier config overrides ESLint stylistic rules that are handled by Prettier config.baseConfig.extends = config.baseConfig.extends.concat('prettier'); // The `prettier/prettier` rule reports errors if the code is not formatted in accordance to Prettier - config.rules['prettier/prettier'] = ['error', mergeWithPrettierConf(opts)]; + config.rules['prettier/prettier'] = [ + 'error', mergeWithPrettierConf(opts, prettier.resolveConfig.sync(opts.cwd || process.cwd()) || {}) + ]; // If the user has the React, Flowtype or Standard plugin, add the corresponding Prettier rule overrides // See https://github.com/prettier/eslint-config-prettier for the list of plugins overrrides for (const override of ['react', 'flowtype', 'standard']) { diff --git a/readme.md b/readme.md index 92d2bfb6..4ad45b6b 100644 --- a/readme.md +++ b/readme.md @@ -206,7 +206,18 @@ Set it to `false` to enforce no-semicolon style. Type: `boolean`
Default: `false` -Format code with [Prettier](https://github.com/prettier/prettier). The [Prettier options](https://prettier.io/docs/en/options.html) will be read from the [Prettier config](https://prettier.io/docs/en/configuration.html) +Format code with [Prettier](https://github.com/prettier/prettier). + +The [Prettier options](https://prettier.io/docs/en/options.html) will be read from the [Prettier config](https://prettier.io/docs/en/configuration.html) and if **not set** will be determined as follow: +- [semi](https://prettier.io/docs/en/options.html#semicolons): based on [semicolon](#semicolon) option +- [useTabs](https://prettier.io/docs/en/options.html#tabs): based on [space](#space) option +- [tabWidth](https://prettier.io/docs/en/options.html#tab-width): based on [space](#space) option +- [trailingComma](https://prettier.io/docs/en/options.html#trailing-commas): based on [esnext](#esnext) +- [singleQuote](https://prettier.io/docs/en/options.html#quotes): `true` +- [bracketSpacing](https://prettier.io/docs/en/options.html#bracket-spacing): `false` +- [jsxBracketSameLine](https://prettier.io/docs/en/options.html#jsx-brackets): `false` + +If contradicting options are set for both Prettier and XO and error will be thrown. ### nodeVersion diff --git a/test/options-manager.js b/test/options-manager.js index 7a6f8be5..b6b0fec2 100644 --- a/test/options-manager.js +++ b/test/options-manager.js @@ -92,6 +92,12 @@ test('buildConfig: prettier: true', t => { }]}); // eslint-prettier-config must always be last t.deepEqual(config.baseConfig.extends.slice(-1), ['prettier']); + // Indent rule is not enabled + t.is(config.rules.indent, undefined); + // Semi rule is not enabled + t.is(config.rules.semi, undefined); + // Semi-spacing is not enabled + t.is(config.rules['semi-spacing'], undefined); }); test('buildConfig: prettier: true, semicolon: false', t => { @@ -107,6 +113,12 @@ test('buildConfig: prettier: true, semicolon: false', t => { tabWidth: 2, trailingComma: 'es5' }]); + // Indent rule is not enabled + t.is(config.rules.indent, undefined); + // Semi rule is not enabled + t.is(config.rules.semi, undefined); + // Semi-spacing is not enabled + t.is(config.rules['semi-spacing'], undefined); }); test('buildConfig: prettier: true, space: 4', t => { @@ -124,6 +136,10 @@ test('buildConfig: prettier: true, space: 4', t => { }]); // Indent rule is not enabled t.is(config.rules.indent, undefined); + // Semi rule is not enabled + t.is(config.rules.semi, undefined); + // Semi-spacing is not enabled + t.is(config.rules['semi-spacing'], undefined); }); test('buildConfig: prettier: true, esnext: false', t => { @@ -139,6 +155,12 @@ test('buildConfig: prettier: true, esnext: false', t => { tabWidth: 2, trailingComma: 'none' }]); + // Indent rule is not enabled + t.is(config.rules.indent, undefined); + // Semi rule is not enabled + t.is(config.rules.semi, undefined); + // Semi-spacing is not enabled + t.is(config.rules['semi-spacing'], undefined); }); test('buildConfig: prettier: true, space: true', t => { @@ -156,6 +178,24 @@ test('buildConfig: prettier: true, space: true', t => { }]); // Indent rule is not enabled t.is(config.rules.indent, undefined); + // Semi rule is not enabled + t.is(config.rules.semi, undefined); + // Semi-spacing is not enabled + t.is(config.rules['semi-spacing'], undefined); +}); + +test('buildConfig: merge with prettier config', t => { + const cwd = path.resolve('fixtures', 'prettier'); + const config = manager.buildConfig({cwd, prettier: true}); + + // Sets the `semi` options in `prettier/prettier` based on the XO `semicolon` option + t.deepEqual(config.rules['prettier/prettier'], ['error', prettierConfig.prettier]); + // Indent rule is not enabled + t.is(config.rules.indent, undefined); + // Semi rule is not enabled + t.is(config.rules.semi, undefined); + // Semi-spacing is not enabled + t.is(config.rules['semi-spacing'], undefined); }); test('buildConfig: engines: undefined', t => { @@ -237,19 +277,69 @@ test('buildConfig: engines: >=8', t => { }); test('mergeWithPrettierConf: use `singleQuote`, `trailingComma`, `bracketSpacing` and `jsxBracketSameLine` from `prettier` config if defined', t => { - const cwd = path.resolve('fixtures', 'prettier'); - const result = manager.mergeWithPrettierConf({cwd}); - const expected = Object.assign({}, prettierConfig.prettier, {tabWidth: 2, useTabs: true, semi: true}); + const prettierOpts = {singleQuote: false, trailingComma: 'all', bracketSpacing: false, jsxBracketSameLine: false}; + const result = manager.mergeWithPrettierConf({}, prettierOpts); + const expected = Object.assign({}, prettierOpts, {tabWidth: 2, useTabs: true, semi: true}); t.deepEqual(result, expected); }); test('mergeWithPrettierConf: determine `tabWidth`, `useTabs`, `semi` from xo config', t => { - const cwd = path.resolve('fixtures', 'prettier'); - const result = manager.mergeWithPrettierConf({cwd, space: 4, semicolon: false}); - const expected = Object.assign({}, prettierConfig.prettier, {tabWidth: 4, useTabs: false, semi: false}); + const prettierOpts = {tabWidth: 4, useTabs: false, semi: false}; + const result = manager.mergeWithPrettierConf({space: 4, semicolon: false}, {}); + const expected = Object.assign( + {bracketSpacing: false, jsxBracketSameLine: false, singleQuote: true, trailingComma: 'es5'}, + prettierOpts + ); + t.deepEqual(result, expected); +}); + +test('mergeWithPrettierConf: determine `tabWidth`, `useTabs`, `semi` from prettier config', t => { + const prettierOpts = {useTabs: false, semi: false, tabWidth: 4}; + const result = manager.mergeWithPrettierConf({}, prettierOpts); + const expected = Object.assign( + {bracketSpacing: false, jsxBracketSameLine: false, singleQuote: true, trailingComma: 'es5'}, + prettierOpts + ); t.deepEqual(result, expected); }); +test('mergeWithPrettierConf: throw error is `semi`/`semicolon` conflicts', t => { + t.throws(() => manager.mergeWithPrettierConf( + {semicolon: true}, + {semi: false} + )); + t.throws(() => manager.mergeWithPrettierConf( + {semicolon: false}, + {semi: true} + )); + + t.notThrows(() => manager.mergeWithPrettierConf( + {semicolon: true}, + {semi: true} + )); + t.notThrows(() => manager.mergeWithPrettierConf({semicolon: false}, {semi: false})); +}); + +test('mergeWithPrettierConf: throw error is `space`/`useTabs` conflicts', t => { + t.throws(() => manager.mergeWithPrettierConf({space: true}, {useTabs: false})); + t.throws(() => manager.mergeWithPrettierConf({space: 4}, {useTabs: false})); + t.throws(() => manager.mergeWithPrettierConf({space: 0}, {useTabs: false})); + t.throws(() => manager.mergeWithPrettierConf({space: false}, {useTabs: true})); + + t.notThrows(() => manager.mergeWithPrettierConf({space: false}, {useTabs: false})); + t.notThrows(() => manager.mergeWithPrettierConf({space: true}, {useTabs: true})); +}); + +test('mergeWithPrettierConf: throw error is `space`/`tabWidth` conflicts', t => { + t.throws(() => manager.mergeWithPrettierConf({space: 4}, {tabWidth: 2})); + t.throws(() => manager.mergeWithPrettierConf({space: 0}, {tabWidth: 2})); + t.throws(() => manager.mergeWithPrettierConf({space: 2}, {tabWidth: 0})); + + t.notThrows(() => manager.mergeWithPrettierConf({space: 4}, {tabWidth: 4})); + t.notThrows(() => manager.mergeWithPrettierConf({space: false}, {tabWidth: 4})); + t.notThrows(() => manager.mergeWithPrettierConf({space: true}, {tabWidth: 4})); +}); + test('buildConfig: rules', t => { const rules = {'object-curly-spacing': ['error', 'always']}; const config = manager.buildConfig({rules});