Skip to content

Commit

Permalink
Add --print-config flag (#529)
Browse files Browse the repository at this point in the history
  • Loading branch information
andersk committed Apr 20, 2021
1 parent e80c094 commit 7b8dc70
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 80 deletions.
25 changes: 22 additions & 3 deletions cli-main.js
Expand Up @@ -30,6 +30,7 @@ const cli = meow(`
--cwd=<dir> Working directory for files
--stdin Validate/fix code from stdin
--stdin-filename Specify a filename for the --stdin option
--print-config Print the effective ESLint config for the given file
Examples
$ xo
Expand All @@ -40,6 +41,7 @@ const cli = meow(`
$ xo --plugin=react
$ xo --plugin=html --extension=html
$ echo 'const x=true' | xo --stdin --fix
$ xo --print-config=index.js
Tips
- Add XO to your project with \`npm init xo\`.
Expand Down Expand Up @@ -99,6 +101,9 @@ const cli = meow(`
cwd: {
type: 'string'
},
printConfig: {
type: 'string'
},
stdin: {
type: 'boolean'
},
Expand Down Expand Up @@ -164,13 +169,27 @@ if (options.nodeVersion) {
if (options.nodeVersion === 'false') {
options.nodeVersion = false;
} else if (!semver.validRange(options.nodeVersion)) {
console.error('The `node-engine` option must be a valid semver range (for example `>=6`)');
console.error('The `--node-engine` flag must be a valid semver range (for example `>=6`)');
process.exit(1);
}
}

(async () => {
if (options.stdin) {
if (options.printConfig) {
if (input.length > 0) {
console.error('The `--print-config` flag must be used with exactly one filename');
process.exit(1);
}

if (options.stdin) {
console.error('The `--print-config` flag is not supported on stdin');
process.exit(1);
}

options.filename = options.printConfig;
const config = xo.getConfig(options);
console.log(JSON.stringify(config, undefined, '\t'));
} else if (options.stdin) {
const stdin = await getStdin();

if (options.stdinFilename) {
Expand All @@ -185,7 +204,7 @@ if (options.nodeVersion) {
}

if (options.open) {
console.error('The `open` option is not supported on stdin');
console.error('The `--open` flag is not supported on stdin');
process.exit(1);
}

Expand Down
8 changes: 8 additions & 0 deletions index.js
Expand Up @@ -57,6 +57,13 @@ const globFiles = async (patterns, {ignores, extensions, cwd}) => (
{ignore: ignores, gitignore: true, cwd}
)).filter(file => extensions.includes(path.extname(file).slice(1))).map(file => path.resolve(cwd, file));

const getConfig = options => {
const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(options));
options = buildConfig(foundOptions, prettierOptions);
const engine = new eslint.CLIEngine(options);
return engine.getConfigForFile(options.filename);
};

const lintText = (string, options) => {
const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(options));
options = buildConfig(foundOptions, prettierOptions);
Expand Down Expand Up @@ -121,6 +128,7 @@ module.exports = {
getFormatter: eslint.CLIEngine.getFormatter,
getErrorResults: eslint.CLIEngine.getErrorResults,
outputFixes: eslint.CLIEngine.outputFixes,
getConfig,
lintText,
lintFiles
};
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -71,6 +71,7 @@ $ xo --help
--cwd=<dir> Working directory for files
--stdin Validate/fix code from stdin
--stdin-filename Specify a filename for the --stdin option
--print-config Print the ESLint configuration for the given file
Examples
$ xo
Expand All @@ -81,6 +82,7 @@ $ xo --help
$ xo --plugin=react
$ xo --plugin=html --extension=html
$ echo 'const x=true' | xo --stdin --fix
$ xo --print-config=index.js
Tips
- Add XO to your project with `npm init xo`.
Expand Down
23 changes: 18 additions & 5 deletions test/cli-main.js
Expand Up @@ -33,11 +33,10 @@ test('stdin-filename option with stdin', async t => {
test('reporter option', async t => {
const filepath = await tempWrite('console.log()\n', 'x.js');

try {
await main(['--reporter=compact', filepath]);
} catch (error) {
t.true(error.stdout.includes('Error - '));
}
const error = await t.throwsAsync(() =>
main(['--reporter=compact', filepath])
);
t.true(error.stdout.includes('Error - '));
});

test('overrides fixture', async t => {
Expand Down Expand Up @@ -167,3 +166,17 @@ test('extension option', async t => {
t.is(reports.length, 1);
t.true(reports[0].filePath.endsWith('.unknown'));
});

test('invalid print-config flag with stdin', async t => {
const error = await t.throwsAsync(() =>
main(['--print-config', 'x.js', '--stdin'], {input: 'console.log()\n'})
);
t.is(error.stderr.trim(), 'The `--print-config` flag is not supported on stdin');
});

test('print-config flag requires a single filename', async t => {
const error = await t.throwsAsync(() =>
main(['--print-config', 'x.js', 'y.js'])
);
t.is(error.stderr.trim(), 'The `--print-config` flag must be used with exactly one filename');
});
44 changes: 22 additions & 22 deletions test/lint-files.js
@@ -1,6 +1,6 @@
import path from 'path';
import test from 'ava';
import fn from '..';
import xo from '..';

process.chdir(__dirname);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

t.true(
hasRule(
Expand Down Expand Up @@ -169,7 +169,7 @@ test('find configurations close to linted file', async t => {
});

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

t.true(
hasRule(
Expand Down Expand Up @@ -197,23 +197,23 @@ test('typescript files', async t => {
});

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

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

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

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

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

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

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

t.true(
hasRule(
Expand Down

0 comments on commit 7b8dc70

Please sign in to comment.