diff --git a/cli-main.js b/cli-main.js deleted file mode 100755 index 6fa2f447..00000000 --- a/cli-main.js +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env node -import getStdin from 'get-stdin'; -import meow from 'meow'; -import formatterPretty from 'eslint-formatter-pretty'; -import semver from 'semver'; -import openReport from './lib/open-report.js'; -import xo from './index.js'; - -const cli = meow(` - Usage - $ xo [ ...] - - Options - --fix Automagically fix issues - --reporter Reporter to use - --env Environment preset [Can be set multiple times] - --global Global variable [Can be set multiple times] - --ignore Additional paths to ignore [Can be set multiple times] - --space Use space indent instead of tabs [Default: 2] - --no-semicolon Prevent use of semicolons - --prettier Conform to Prettier code style - --node-version Range of Node.js version to support - --plugin Include third-party plugins [Can be set multiple times] - --extend Extend defaults with a custom config [Can be set multiple times] - --open Open files with issues in your editor - --quiet Show only errors and no warnings - --extension Additional extension to lint [Can be set multiple times] - --cwd= 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 - $ xo index.js - $ xo *.js !foo.js - $ xo --space - $ xo --env=node --env=mocha - $ 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\`. - - Put options in package.json instead of using flags so other tools can read it. -`, { - autoVersion: false, - booleanDefault: undefined, - flags: { - fix: { - type: 'boolean' - }, - reporter: { - type: 'string' - }, - env: { - type: 'string', - isMultiple: true - }, - global: { - type: 'string', - isMultiple: true - }, - ignore: { - type: 'string', - isMultiple: true - }, - space: { - type: 'string' - }, - semicolon: { - type: 'boolean' - }, - prettier: { - type: 'boolean' - }, - nodeVersion: { - type: 'string' - }, - plugin: { - type: 'string', - isMultiple: true - }, - extend: { - type: 'string', - isMultiple: true - }, - open: { - type: 'boolean' - }, - quiet: { - type: 'boolean' - }, - extension: { - type: 'string', - isMultiple: true - }, - cwd: { - type: 'string' - }, - printConfig: { - type: 'string' - }, - stdin: { - type: 'boolean' - }, - stdinFilename: { - type: 'string' - } - } -}); - -const {input, flags: options, showVersion} = cli; - -// TODO: Fix this properly instead of the below workaround. -// Revert behavior of meow >8 to pre-8 (7.1.1) for flags using `isMultiple: true`. -// Otherwise, options defined in package.json can't be merged by lib/options-manager.js `mergeOptions()`. -for (const key in options) { - if (Array.isArray(options[key]) && options[key].length === 0) { - delete options[key]; - } -} - -// Make data types for `options.space` match those of the API -// Check for string type because `xo --no-space` sets `options.space` to `false` -if (typeof options.space === 'string') { - if (/^\d+$/u.test(options.space)) { - options.space = Number.parseInt(options.space, 10); - } else if (options.space === 'true') { - options.space = true; - } else if (options.space === 'false') { - options.space = false; - } else { - if (options.space !== '') { - // Assume `options.space` was set to a filename when run as `xo --space file.js` - input.push(options.space); - } - - options.space = true; - } -} - -if (process.env.GITHUB_ACTIONS && !options.fix && !options.reporter) { - options.quiet = true; -} - -const log = async report => { - const reporter = options.reporter || process.env.GITHUB_ACTIONS ? await xo.getFormatter(options.reporter || 'compact') : formatterPretty; - process.stdout.write(reporter(report.results)); - process.exitCode = report.errorCount === 0 ? 0 : 1; -}; - -// `xo -` => `xo --stdin` -if (input[0] === '-') { - options.stdin = true; - input.shift(); -} - -if (options.version) { - showVersion(); -} - -if (options.nodeVersion) { - if (options.nodeVersion === 'false') { - options.nodeVersion = false; - } else if (!semver.validRange(options.nodeVersion)) { - console.error('The `--node-engine` flag must be a valid semver range (for example `>=6`)'); - process.exit(1); - } -} - -(async () => { - 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.filePath = options.printConfig; - const config = await xo.getConfig(options); - console.log(JSON.stringify(config, undefined, '\t')); - } else if (options.stdin) { - const stdin = await getStdin(); - - if (options.stdinFilename) { - options.filePath = options.stdinFilename; - } - - if (options.fix) { - const {results: [result]} = await xo.lintText(stdin, options); - // If there is no output, pass the stdin back out - process.stdout.write((result && result.output) || stdin); - return; - } - - if (options.open) { - console.error('The `--open` flag is not supported on stdin'); - process.exit(1); - } - - await log(await xo.lintText(stdin, options)); - } else { - const report = await xo.lintFiles(input, options); - - if (options.fix) { - await xo.outputFixes(report); - } - - if (options.open) { - openReport(report); - } - - await log(report); - } -})(); diff --git a/test/cli-main.js b/test/cli-main.js deleted file mode 100644 index 51999728..00000000 --- a/test/cli-main.js +++ /dev/null @@ -1,184 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import test from 'ava'; -import execa from 'execa'; -import slash from 'slash'; -import tempWrite from 'temp-write'; -import createEsmUtils from 'esm-utils'; - -const {__dirname} = createEsmUtils(import.meta); -process.chdir(__dirname); - -const main = (arguments_, options) => execa(path.join(__dirname, '../cli-main.js'), arguments_, options); - -test('fix option', async t => { - const filepath = await tempWrite('console.log()\n', 'x.js'); - await main(['--fix', filepath]); - t.is(fs.readFileSync(filepath, 'utf8').trim(), 'console.log();'); -}); - -test('fix option with stdin', async t => { - const {stdout} = await main(['--fix', '--stdin'], { - input: 'console.log()' - }); - t.is(stdout, 'console.log();'); -}); - -test('stdin-filename option with stdin', async t => { - const {stdout} = await main(['--stdin', '--stdin-filename=unicorn-file'], { - input: 'console.log()\n', - reject: false - }); - t.regex(stdout, /unicorn-file:/u); -}); - -test('reporter option', async t => { - const filepath = await tempWrite('console.log()\n', 'x.js'); - - const error = await t.throwsAsync(() => - main(['--reporter=compact', filepath]) - ); - t.true(error.stdout.includes('Error - ')); -}); - -test('overrides fixture', async t => { - const cwd = path.join(__dirname, 'fixtures/overrides'); - await t.notThrowsAsync(main([], {cwd})); -}); - -test('overrides work with relative path', async t => { - const cwd = path.join(__dirname, 'fixtures/overrides'); - const file = path.join('test', 'bar.js'); - await t.notThrowsAsync(main([file], {cwd})); -}); - -test('overrides work with relative path starting with `./`', async t => { - const cwd = path.join(__dirname, 'fixtures/overrides'); - const file = '.' + path.sep + path.join('test', 'bar.js'); - await t.notThrowsAsync(main([file], {cwd})); -}); - -test('overrides work with absolute path', async t => { - const cwd = path.join(__dirname, 'fixtures/overrides'); - const file = path.join(cwd, 'test', 'bar.js'); - await t.notThrowsAsync(main([file], {cwd})); -}); - -test.failing('override default ignore', async t => { - const cwd = path.join(__dirname, 'fixtures/ignores'); - await t.throwsAsync(main([], {cwd})); -}); - -test('ignore files in .gitignore', async t => { - const cwd = path.join(__dirname, 'fixtures/gitignore'); - const error = await t.throwsAsync(main(['--reporter=json'], {cwd})); - const reports = JSON.parse(error.stdout); - const files = reports - .map(report => path.relative(cwd, report.filePath)) - .map(report => slash(report)); - t.deepEqual(files.sort(), ['index.js', 'test/bar.js'].sort()); -}); - -test('ignore explicit files when in .gitgnore', async t => { - const cwd = path.join(__dirname, 'fixtures/gitignore'); - await t.notThrowsAsync(main(['test/foo.js', '--reporter=json'], {cwd})); -}); - -test('negative gitignores', async t => { - const cwd = path.join(__dirname, 'fixtures/negative-gitignore'); - const error = await t.throwsAsync(main(['--reporter=json'], {cwd})); - const reports = JSON.parse(error.stdout); - const files = reports.map(report => path.relative(cwd, report.filePath)); - t.deepEqual(files, ['foo.js']); -}); - -test('supports being extended with a shareable config', async t => { - const cwd = path.join(__dirname, 'fixtures/project'); - await t.notThrowsAsync(main([], {cwd})); -}); - -test('quiet option', async t => { - const filepath = await tempWrite('// TODO: quiet\nconsole.log()\n', 'x.js'); - const error = await t.throwsAsync(main(['--quiet', '--reporter=json', filepath])); - const [report] = JSON.parse(error.stdout); - t.is(report.warningCount, 0); -}); - -test('invalid node-engine option', async t => { - const filepath = await tempWrite('console.log()\n', 'x.js'); - const error = await t.throwsAsync(main(['--node-version', 'v', filepath])); - t.is(error.exitCode, 1); -}); - -test('cli option takes precedence over config', async t => { - const cwd = path.join(__dirname, 'fixtures/default-options'); - const input = 'console.log()\n'; - - // Use config from package.json - await t.notThrowsAsync(main(['--stdin'], {cwd, input})); - - // Override package.json config with cli flag - await t.throwsAsync(main(['--semicolon=true', '--stdin'], {cwd, input})); - - // Use XO default (`true`) even if option is not set in package.json nor cli arg - // i.e make sure absent cli flags are not parsed as `false` - await t.throwsAsync(main(['--stdin'], {input})); -}); - -test('space option with number value', async t => { - const cwd = path.join(__dirname, 'fixtures/space'); - const {stdout} = await t.throwsAsync(main(['--space=4', 'one-space.js'], {cwd})); - t.true(stdout.includes('Expected indentation of 4 spaces')); -}); - -test('space option as boolean', async t => { - const cwd = path.join(__dirname, 'fixtures/space'); - const {stdout} = await t.throwsAsync(main(['--space'], {cwd})); - t.true(stdout.includes('Expected indentation of 2 spaces')); -}); - -test('space option as boolean with filename', async t => { - const cwd = path.join(__dirname, 'fixtures/space'); - const {stdout} = await main(['--reporter=json', '--space', 'two-spaces.js'], { - cwd, - reject: false - }); - const reports = JSON.parse(stdout); - - // Only the specified file was checked (filename was not the value of `space`) - t.is(reports.length, 1); - - // The default space value of 2 was expected - t.is(reports[0].errorCount, 0); -}); - -test('space option with boolean strings', async t => { - const cwd = path.join(__dirname, 'fixtures/space'); - const trueResult = await t.throwsAsync(main(['--space=true'], {cwd})); - const falseResult = await t.throwsAsync(main(['--space=false'], {cwd})); - t.true(trueResult.stdout.includes('Expected indentation of 2 spaces')); - t.true(falseResult.stdout.includes('Expected indentation of 1 tab')); -}); - -test('extension option', async t => { - const cwd = path.join(__dirname, 'fixtures/custom-extension'); - const {stdout} = await t.throwsAsync(main(['--reporter=json', '--extension=unknown'], {cwd})); - const reports = JSON.parse(stdout); - - 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'); -});