Skip to content

Commit

Permalink
Make Prettier confing take precedence over XO (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg authored and sindresorhus committed Feb 17, 2018
1 parent 3094538 commit c62345c
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 11 deletions.
24 changes: 20 additions & 4 deletions lib/options-manager.js
Expand Up @@ -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(
{},
{
Expand All @@ -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
);
};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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']) {
Expand Down
13 changes: 12 additions & 1 deletion readme.md
Expand Up @@ -206,7 +206,18 @@ Set it to `false` to enforce no-semicolon style.
Type: `boolean`<br>
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

Expand Down
102 changes: 96 additions & 6 deletions test/options-manager.js
Expand Up @@ -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 => {
Expand All @@ -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 => {
Expand All @@ -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 => {
Expand All @@ -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 => {
Expand All @@ -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 => {
Expand Down Expand Up @@ -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});
Expand Down

0 comments on commit c62345c

Please sign in to comment.