Skip to content

Commit 7b8dc70

Browse files
authored
Add --print-config flag (#529)
1 parent e80c094 commit 7b8dc70

File tree

8 files changed

+148
-80
lines changed

8 files changed

+148
-80
lines changed

cli-main.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const cli = meow(`
3030
--cwd=<dir> Working directory for files
3131
--stdin Validate/fix code from stdin
3232
--stdin-filename Specify a filename for the --stdin option
33+
--print-config Print the effective ESLint config for the given file
3334
3435
Examples
3536
$ xo
@@ -40,6 +41,7 @@ const cli = meow(`
4041
$ xo --plugin=react
4142
$ xo --plugin=html --extension=html
4243
$ echo 'const x=true' | xo --stdin --fix
44+
$ xo --print-config=index.js
4345
4446
Tips
4547
- Add XO to your project with \`npm init xo\`.
@@ -99,6 +101,9 @@ const cli = meow(`
99101
cwd: {
100102
type: 'string'
101103
},
104+
printConfig: {
105+
type: 'string'
106+
},
102107
stdin: {
103108
type: 'boolean'
104109
},
@@ -164,13 +169,27 @@ if (options.nodeVersion) {
164169
if (options.nodeVersion === 'false') {
165170
options.nodeVersion = false;
166171
} else if (!semver.validRange(options.nodeVersion)) {
167-
console.error('The `node-engine` option must be a valid semver range (for example `>=6`)');
172+
console.error('The `--node-engine` flag must be a valid semver range (for example `>=6`)');
168173
process.exit(1);
169174
}
170175
}
171176

172177
(async () => {
173-
if (options.stdin) {
178+
if (options.printConfig) {
179+
if (input.length > 0) {
180+
console.error('The `--print-config` flag must be used with exactly one filename');
181+
process.exit(1);
182+
}
183+
184+
if (options.stdin) {
185+
console.error('The `--print-config` flag is not supported on stdin');
186+
process.exit(1);
187+
}
188+
189+
options.filename = options.printConfig;
190+
const config = xo.getConfig(options);
191+
console.log(JSON.stringify(config, undefined, '\t'));
192+
} else if (options.stdin) {
174193
const stdin = await getStdin();
175194

176195
if (options.stdinFilename) {
@@ -185,7 +204,7 @@ if (options.nodeVersion) {
185204
}
186205

187206
if (options.open) {
188-
console.error('The `open` option is not supported on stdin');
207+
console.error('The `--open` flag is not supported on stdin');
189208
process.exit(1);
190209
}
191210

index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ const globFiles = async (patterns, {ignores, extensions, cwd}) => (
5757
{ignore: ignores, gitignore: true, cwd}
5858
)).filter(file => extensions.includes(path.extname(file).slice(1))).map(file => path.resolve(cwd, file));
5959

60+
const getConfig = options => {
61+
const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(options));
62+
options = buildConfig(foundOptions, prettierOptions);
63+
const engine = new eslint.CLIEngine(options);
64+
return engine.getConfigForFile(options.filename);
65+
};
66+
6067
const lintText = (string, options) => {
6168
const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(options));
6269
options = buildConfig(foundOptions, prettierOptions);
@@ -121,6 +128,7 @@ module.exports = {
121128
getFormatter: eslint.CLIEngine.getFormatter,
122129
getErrorResults: eslint.CLIEngine.getErrorResults,
123130
outputFixes: eslint.CLIEngine.outputFixes,
131+
getConfig,
124132
lintText,
125133
lintFiles
126134
};

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ $ xo --help
7171
--cwd=<dir> Working directory for files
7272
--stdin Validate/fix code from stdin
7373
--stdin-filename Specify a filename for the --stdin option
74+
--print-config Print the ESLint configuration for the given file
7475
7576
Examples
7677
$ xo
@@ -81,6 +82,7 @@ $ xo --help
8182
$ xo --plugin=react
8283
$ xo --plugin=html --extension=html
8384
$ echo 'const x=true' | xo --stdin --fix
85+
$ xo --print-config=index.js
8486
8587
Tips
8688
- Add XO to your project with `npm init xo`.

test/cli-main.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@ test('stdin-filename option with stdin', async t => {
3333
test('reporter option', async t => {
3434
const filepath = await tempWrite('console.log()\n', 'x.js');
3535

36-
try {
37-
await main(['--reporter=compact', filepath]);
38-
} catch (error) {
39-
t.true(error.stdout.includes('Error - '));
40-
}
36+
const error = await t.throwsAsync(() =>
37+
main(['--reporter=compact', filepath])
38+
);
39+
t.true(error.stdout.includes('Error - '));
4140
});
4241

4342
test('overrides fixture', async t => {
@@ -167,3 +166,17 @@ test('extension option', async t => {
167166
t.is(reports.length, 1);
168167
t.true(reports[0].filePath.endsWith('.unknown'));
169168
});
169+
170+
test('invalid print-config flag with stdin', async t => {
171+
const error = await t.throwsAsync(() =>
172+
main(['--print-config', 'x.js', '--stdin'], {input: 'console.log()\n'})
173+
);
174+
t.is(error.stderr.trim(), 'The `--print-config` flag is not supported on stdin');
175+
});
176+
177+
test('print-config flag requires a single filename', async t => {
178+
const error = await t.throwsAsync(() =>
179+
main(['--print-config', 'x.js', 'y.js'])
180+
);
181+
t.is(error.stderr.trim(), 'The `--print-config` flag must be used with exactly one filename');
182+
});

test/lint-files.js

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'path';
22
import test from 'ava';
3-
import fn from '..';
3+
import xo from '..';
44

55
process.chdir(__dirname);
66

@@ -14,18 +14,18 @@ test('only accepts allowed extensions', async t => {
1414
const mdGlob = path.join(__dirname, '..', '*.md');
1515

1616
// No files should be linted = no errors
17-
const noOptionsResults = await fn.lintFiles(mdGlob, {});
17+
const noOptionsResults = await xo.lintFiles(mdGlob, {});
1818
t.is(noOptionsResults.errorCount, 0);
1919

2020
// Markdown files linted (with no plugin for it) = errors
21-
const moreExtensionsResults = await fn.lintFiles(mdGlob, {extensions: ['md']});
21+
const moreExtensionsResults = await xo.lintFiles(mdGlob, {extensions: ['md']});
2222
t.true(moreExtensionsResults.errorCount > 0);
2323
});
2424

2525
test('ignores dirs for empty extensions', async t => {
2626
{
2727
const glob = path.join(__dirname, 'fixtures/nodir/*');
28-
const results = await fn.lintFiles(glob, {extensions: ['', 'js']});
28+
const results = await xo.lintFiles(glob, {extensions: ['', 'js']});
2929
const {results: [fileResult]} = results;
3030

3131
// Only `fixtures/nodir/noextension` should be linted
@@ -37,7 +37,7 @@ test('ignores dirs for empty extensions', async t => {
3737

3838
{
3939
const glob = path.join(__dirname, 'fixtures/nodir/nested/*');
40-
const results = await fn.lintFiles(glob);
40+
const results = await xo.lintFiles(glob);
4141
const {results: [fileResult]} = results;
4242

4343
// Ensure `nodir/nested` **would** report if globbed
@@ -49,7 +49,7 @@ test('ignores dirs for empty extensions', async t => {
4949
});
5050

5151
test.serial('cwd option', async t => {
52-
const {results} = await fn.lintFiles('**/*', {cwd: 'fixtures/cwd'});
52+
const {results} = await xo.lintFiles('**/*', {cwd: 'fixtures/cwd'});
5353
const paths = results.map(r => path.relative(__dirname, r.filePath));
5454
paths.sort();
5555
t.deepEqual(paths, [path.join('fixtures', 'cwd', 'unicorn.js')]);
@@ -59,7 +59,7 @@ test('do not lint gitignored files', async t => {
5959
const cwd = path.join(__dirname, 'fixtures/gitignore');
6060
const glob = path.posix.join(cwd, '**/*');
6161
const ignored = path.resolve('fixtures/gitignore/test/foo.js');
62-
const {results} = await fn.lintFiles(glob, {cwd});
62+
const {results} = await xo.lintFiles(glob, {cwd});
6363

6464
t.is(results.some(r => r.filePath === ignored), false);
6565
});
@@ -68,7 +68,7 @@ test('do not lint gitignored files in file with negative gitignores', async t =>
6868
const cwd = path.join(__dirname, 'fixtures/negative-gitignore');
6969
const glob = path.posix.join(cwd, '*');
7070
const ignored = path.resolve('fixtures/negative-gitignore/bar.js');
71-
const {results} = await fn.lintFiles(glob, {cwd});
71+
const {results} = await xo.lintFiles(glob, {cwd});
7272

7373
t.is(results.some(r => r.filePath === ignored), false);
7474
});
@@ -77,7 +77,7 @@ test('lint negatively gitignored files', async t => {
7777
const cwd = path.join(__dirname, 'fixtures/negative-gitignore');
7878
const glob = path.posix.join(cwd, '*');
7979
const negative = path.resolve('fixtures/negative-gitignore/foo.js');
80-
const {results} = await fn.lintFiles(glob, {cwd});
80+
const {results} = await xo.lintFiles(glob, {cwd});
8181

8282
t.is(results.some(r => r.filePath === negative), true);
8383
});
@@ -86,22 +86,22 @@ test('do not lint inapplicable negatively gitignored files', async t => {
8686
const cwd = path.join(__dirname, 'fixtures/negative-gitignore');
8787
const glob = path.posix.join(cwd, 'bar.js');
8888
const negative = path.resolve('fixtures/negative-gitignore/foo.js');
89-
const {results} = await fn.lintFiles(glob, {cwd});
89+
const {results} = await xo.lintFiles(glob, {cwd});
9090

9191
t.is(results.some(r => r.filePath === negative), false);
9292
});
9393

9494
test('multiple negative patterns should act as positive patterns', async t => {
9595
const cwd = path.join(__dirname, 'fixtures', 'gitignore-multiple-negation');
96-
const {results} = await fn.lintFiles('**/*', {cwd});
96+
const {results} = await xo.lintFiles('**/*', {cwd});
9797
const paths = results.map(r => path.basename(r.filePath));
9898
paths.sort();
9999

100100
t.deepEqual(paths, ['!!unicorn.js', '!unicorn.js']);
101101
});
102102

103103
test('enable rules based on nodeVersion', async t => {
104-
const {results} = await fn.lintFiles('**/*', {cwd: 'fixtures/engines-overrides'});
104+
const {results} = await xo.lintFiles('**/*', {cwd: 'fixtures/engines-overrides'});
105105

106106
// The transpiled file (as specified in `overrides`) should use `await`
107107
t.true(
@@ -126,14 +126,14 @@ test('do not lint eslintignored files', async t => {
126126
const glob = path.posix.join(cwd, '*');
127127
const positive = path.resolve('fixtures/eslintignore/foo.js');
128128
const negative = path.resolve('fixtures/eslintignore/bar.js');
129-
const {results} = await fn.lintFiles(glob, {cwd});
129+
const {results} = await xo.lintFiles(glob, {cwd});
130130

131131
t.is(results.some(r => r.filePath === positive), true);
132132
t.is(results.some(r => r.filePath === negative), false);
133133
});
134134

135135
test('find configurations close to linted file', async t => {
136-
const {results} = await fn.lintFiles('**/*', {cwd: 'fixtures/nested-configs'});
136+
const {results} = await xo.lintFiles('**/*', {cwd: 'fixtures/nested-configs'});
137137

138138
t.true(
139139
hasRule(
@@ -169,7 +169,7 @@ test('find configurations close to linted file', async t => {
169169
});
170170

171171
test('typescript files', async t => {
172-
const {results} = await fn.lintFiles('**/*', {cwd: 'fixtures/typescript'});
172+
const {results} = await xo.lintFiles('**/*', {cwd: 'fixtures/typescript'});
173173

174174
t.true(
175175
hasRule(
@@ -197,23 +197,23 @@ test('typescript files', async t => {
197197
});
198198

199199
test('typescript 2 space option', async t => {
200-
const {errorCount, results} = await fn.lintFiles('two-spaces.tsx', {cwd: 'fixtures/typescript', space: 2});
200+
const {errorCount, results} = await xo.lintFiles('two-spaces.tsx', {cwd: 'fixtures/typescript', space: 2});
201201
t.is(errorCount, 0, JSON.stringify(results[0].messages));
202202
});
203203

204204
test('typescript 4 space option', async t => {
205-
const {errorCount} = await fn.lintFiles('child/sub-child/four-spaces.ts', {cwd: 'fixtures/typescript', space: 4});
205+
const {errorCount} = await xo.lintFiles('child/sub-child/four-spaces.ts', {cwd: 'fixtures/typescript', space: 4});
206206
t.is(errorCount, 0);
207207
});
208208

209209
test('typescript no semicolon option', async t => {
210-
const {errorCount} = await fn.lintFiles('child/no-semicolon.ts', {cwd: 'fixtures/typescript', semicolon: false});
210+
const {errorCount} = await xo.lintFiles('child/no-semicolon.ts', {cwd: 'fixtures/typescript', semicolon: false});
211211
t.is(errorCount, 0);
212212
});
213213

214214
test('webpack import resolver is used if webpack.config.js is found', async t => {
215215
const cwd = 'fixtures/webpack/with-config/';
216-
const {results} = await fn.lintFiles(path.resolve(cwd, 'file1.js'), {
216+
const {results} = await xo.lintFiles(path.resolve(cwd, 'file1.js'), {
217217
cwd,
218218
rules: {
219219
'import/no-unresolved': 2
@@ -229,7 +229,7 @@ test('webpack import resolver is used if webpack.config.js is found', async t =>
229229
test('webpack import resolver config can be passed through webpack option', async t => {
230230
const cwd = 'fixtures/webpack/no-config/';
231231

232-
const {results} = await fn.lintFiles(path.resolve(cwd, 'file1.js'), {
232+
const {results} = await xo.lintFiles(path.resolve(cwd, 'file1.js'), {
233233
cwd,
234234
webpack: {
235235
config: {
@@ -251,7 +251,7 @@ test('webpack import resolver config can be passed through webpack option', asyn
251251
test('webpack import resolver is used if {webpack: true}', async t => {
252252
const cwd = 'fixtures/webpack/no-config/';
253253

254-
const {results} = await fn.lintFiles(path.resolve(cwd, 'file3.js'), {
254+
const {results} = await xo.lintFiles(path.resolve(cwd, 'file3.js'), {
255255
cwd,
256256
webpack: true,
257257
rules: {
@@ -264,7 +264,7 @@ test('webpack import resolver is used if {webpack: true}', async t => {
264264
});
265265

266266
async function configType(t, {dir}) {
267-
const {results} = await fn.lintFiles('**/*', {cwd: path.resolve('fixtures', 'config-files', dir)});
267+
const {results} = await xo.lintFiles('**/*', {cwd: path.resolve('fixtures', 'config-files', dir)});
268268

269269
t.true(
270270
hasRule(

0 commit comments

Comments
 (0)