diff --git a/README.md b/README.md index 698be99..31be62c 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,7 @@ module.exports = { This function is called with the current ESLint config (the options passed to [ESLint's `CLIEngine`](http://eslint.org/docs/developer-guide/nodejs-api#cliengine)), the options object (`opts`), any options extracted from the project's `package.json` (`packageOpts`), and the directory that contained that `package.json` file (`rootDir`, equivalent to `opts.cwd` if no file was found). -Modify and return `eslintConfig`, or return a new object with the eslint config to be used. +Modify and return `eslintConfig`, or return a new object with the eslint config to be used, either directly or wrapped in a `Promise`. ## API Usage diff --git a/bin/cmd.js b/bin/cmd.js index 5b8666f..385e2cf 100755 --- a/bin/cmd.js +++ b/bin/cmd.js @@ -14,8 +14,9 @@ const getStdin = require('get-stdin') /** * @param {Omit & StandardCliOptions} rawOpts + * @returns {Promise} */ -function cli (rawOpts) { +async function cli (rawOpts) { const opts = { cmd: 'standard-engine', tagline: 'JavaScript Custom Style', @@ -99,90 +100,35 @@ Flags (advanced): parser: argv.parser } - /** @type {string} */ - let stdinText - - if (argv.stdin) { - getStdin().then(function (text) { - stdinText = text - standard.lintText(text, lintOpts, onResult) - }) - } else { - standard.lintFiles(argv._, lintOpts, onResult) - } - - /** @type {import('../').LinterCallback} */ - function onResult (err, result) { - if (err) return onError(err) - if (!result) throw new Error('expected a result') - - if (argv.stdin && argv.fix) { - if (result.results[0] && result.results[0].output) { - // Code contained fixable errors, so print the fixed code - process.stdout.write(result.results[0].output) - } else { - // Code did not contain fixable errors, so print original code - process.stdout.write(stdinText) - } - } + const outputFixed = argv.stdin && argv.fix - if (!result.errorCount && !result.warningCount) { - process.exitCode = 0 - return + /** + * Print lint errors to stdout -- this is expected output from `standard-engine`. + * Note: When fixing code from stdin (`standard --stdin --fix`), the transformed + * code is printed to stdout, so print lint errors to stderr in this case. + * @type {typeof console.log} + */ + const log = (...args) => { + if (outputFixed) { + args[0] = opts.cmd + ': ' + args[0] + console.error.apply(console, args) + } else { + console.log.apply(console, args) } + } - console.error('%s: %s (%s)', opts.cmd, opts.tagline, opts.homepage) - - // Are any warnings present? - const isSomeWarnings = result.results.some(function (result) { - return result.messages.some(function (message) { - return message.severity === 1 - }) - }) - - if (isSomeWarnings) { - const homepage = opts.homepage != null ? ` (${opts.homepage})` : '' - console.error( - '%s: %s', - opts.cmd, - `Some warnings are present which will be errors in the next version${homepage}` - ) - } + /** @type {string} */ + const stdinText = argv.stdin ? await getStdin() : '' + /** @type {import('eslint').CLIEngine.LintReport} */ + let result - // Are any fixable rules present? - const isSomeFixable = result.results.some(function (result) { - return result.messages.some(function (message) { - return !!message.fix - }) - }) - - if (isSomeFixable) { - console.error( - '%s: %s', - opts.cmd, - 'Run `' + opts.cmd + ' --fix` to automatically fix some problems.' - ) + try { + if (argv.stdin) { + result = await standard.lintText(stdinText, lintOpts) + } else { + result = await standard.lintFiles(argv._, lintOpts) } - - result.results.forEach(function (result) { - result.messages.forEach(function (message) { - log( - ' %s:%d:%d: %s%s%s', - result.filePath, - message.line || 0, - message.column || 0, - message.message, - ' (' + message.ruleId + ')', - message.severity === 1 ? ' (warning)' : '' - ) - }) - }) - - process.exitCode = result.errorCount ? 1 : 0 - } - - /** @param {Error|unknown} err */ - function onError (err) { + } catch (err) { console.error(opts.cmd + ': Unexpected linter output:\n') if (err instanceof Error) { console.error(err.stack || err.message) @@ -195,22 +141,66 @@ Flags (advanced): opts.bugs ) process.exitCode = 1 + return } - /** - * Print lint errors to stdout -- this is expected output from `standard-engine`. - * Note: When fixing code from stdin (`standard --stdin --fix`), the transformed - * code is printed to stdout, so print lint errors to stderr in this case. - * @type {typeof console.log} - */ - function log (...args) { - if (argv.stdin && argv.fix) { - args[0] = opts.cmd + ': ' + args[0] - console.error.apply(console, args) + if (!result) throw new Error('expected a result') + + if (outputFixed) { + if (result.results[0] && result.results[0].output) { + // Code contained fixable errors, so print the fixed code + process.stdout.write(result.results[0].output) } else { - console.log.apply(console, args) + // Code did not contain fixable errors, so print original code + process.stdout.write(stdinText) + } + } + + if (!result.errorCount && !result.warningCount) { + process.exitCode = 0 + return + } + + console.error('%s: %s (%s)', opts.cmd, opts.tagline, opts.homepage) + + // Are any warnings present? + const isSomeWarnings = result.results.some(item => item.messages.some(message => message.severity === 1)) + + if (isSomeWarnings) { + const homepage = opts.homepage != null ? ` (${opts.homepage})` : '' + console.error( + '%s: %s', + opts.cmd, + `Some warnings are present which will be errors in the next version${homepage}` + ) + } + + // Are any fixable rules present? + const isSomeFixable = result.results.some(item => item.messages.some(message => !!message.fix)) + + if (isSomeFixable) { + console.error( + '%s: %s', + opts.cmd, + 'Run `' + opts.cmd + ' --fix` to automatically fix some problems.' + ) + } + + for (const item of result.results) { + for (const message of item.messages) { + log( + ' %s:%d:%d: %s%s%s', + item.filePath, + message.line || 0, + message.column || 0, + message.message, + ' (' + message.ruleId + ')', + message.severity === 1 ? ' (warning)' : '' + ) } } + + process.exitCode = result.errorCount ? 1 : 0 } module.exports = cli diff --git a/index.js b/index.js index 14ee6c3..b007ec8 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,6 @@ const { resolveEslintConfig } = require('./lib/resolve-eslint-config') /** @typedef {ConstructorParameters[0]} CLIEngineOptions */ /** @typedef {Omit} BaseLintOptions */ -/** @typedef {(err: Error|unknown, result?: import('eslint').CLIEngine.LintReport) => void} LinterCallback */ /** * @typedef LinterOptions @@ -69,64 +68,34 @@ class Linter { * * @param {string} text file text to lint * @param {Omit & { filename?: string }} [opts] base options + path of file containing the text being linted - * @returns {import('eslint').CLIEngine.LintReport} + * @returns {Promise} */ - lintTextSync (text, opts) { - const eslintConfig = this.resolveEslintConfig(opts) + async lintText (text, opts) { + const eslintConfig = await this.resolveEslintConfig(opts) const engine = new this.eslint.CLIEngine(eslintConfig) return engine.executeOnText(text, (opts || {}).filename) } - /** - * Lint text to enforce JavaScript Style. - * - * @param {string} text file text to lint - * @param {Omit & { filename?: string }} [opts] base options + path of file containing the text being linted - * @param {LinterCallback} [cb] - * @returns {void} - */ - lintText (text, opts, cb) { - if (typeof opts === 'function') return this.lintText(text, undefined, opts) - if (!cb) throw new Error('callback is required') - - let result - try { - result = this.lintTextSync(text, opts) - } catch (err) { - return process.nextTick(cb, err) - } - process.nextTick(cb, null, result) - } - /** * Lint files to enforce JavaScript Style. * - * @param {Array.} files file globs to lint + * @param {Array} files file globs to lint * @param {BaseLintOptions & { cwd?: string }} [opts] base options + file globs to ignore (has sane defaults) + current working directory (default: process.cwd()) - * @param {LinterCallback} [cb] - * @returns {void} + * @returns {Promise} */ - lintFiles (files, opts, cb) { - if (typeof opts === 'function') { return this.lintFiles(files, undefined, opts) } - if (!cb) throw new Error('callback is required') - + async lintFiles (files, opts) { const eslintConfig = this.resolveEslintConfig(opts) if (typeof files === 'string') files = [files] if (files.length === 0) files = ['.'] - let result - try { - result = new this.eslint.CLIEngine(eslintConfig).executeOnFiles(files) - } catch (err) { - return cb(err) - } + const result = new this.eslint.CLIEngine(eslintConfig).executeOnFiles(files) if (eslintConfig.fix) { this.eslint.CLIEngine.outputFixes(result) } - return cb(null, result) + return result } /** diff --git a/test/api.js b/test/api.js index dd51d2b..57b9f2c 100644 --- a/test/api.js +++ b/test/api.js @@ -4,54 +4,42 @@ const test = require('tape') const { Linter } = require('../') -function getStandard () { +async function getStandard () { return new Linter({ cmd: 'pocketlint', version: '0.0.0', eslint, - eslintConfig: require('../tmp/standard/options').eslintConfig + eslintConfig: (await import('../tmp/standard/options.js')).default.eslintConfig }) } -test('api: lintFiles', function (t) { - t.plan(3) - const standard = getStandard() - standard.lintFiles([], { cwd: path.join(__dirname, '../bin') }, function (err, result) { - t.error(err, 'no error while linting') - t.equal(typeof result, 'object', 'result is an object') - t.equal(result.errorCount, 0) - }) -}) - -test('api: lintText', function (t) { - t.plan(3) - const standard = getStandard() - standard.lintText('console.log("hi there")\n', function (err, result) { - t.error(err, 'no error while linting') - t.equal(typeof result, 'object', 'result is an object') - t.equal(result.errorCount, 1, 'should have used single quotes') - }) +test('api: lintFiles', async function (t) { + t.plan(2) + const standard = await getStandard() + const result = await standard.lintFiles([], { cwd: path.join(__dirname, '../bin') }) + t.equal(typeof result, 'object', 'result is an object') + t.equal(result.errorCount, 0) }) -test('api: lintTextSync', function (t) { +test('api: lintText', async function (t) { t.plan(2) - const standard = getStandard() - const result = standard.lintTextSync('console.log("hi there")\n') + const standard = await getStandard() + const result = await standard.lintText('console.log("hi there")\n') t.equal(typeof result, 'object', 'result is an object') t.equal(result.errorCount, 1, 'should have used single quotes') }) -test('api: resolveEslintConfig -- avoid this.eslintConfig parser mutation', function (t) { +test('api: resolveEslintConfig -- avoid this.eslintConfig parser mutation', async function (t) { t.plan(2) - const standard = getStandard() + const standard = await getStandard() const opts = standard.resolveEslintConfig({ parser: 'blah' }) t.equal(opts.parser, 'blah') t.equal(standard.eslintConfig.parser, undefined) }) -test('api: resolveEslintConfig -- avoid this.eslintConfig global mutation', function (t) { +test('api: resolveEslintConfig -- avoid this.eslintConfig global mutation', async function (t) { t.plan(2) - const standard = getStandard() + const standard = await getStandard() const opts = standard.resolveEslintConfig({ globals: ['what'] }) t.deepEqual(opts.globals, ['what']) t.deepEqual(standard.eslintConfig.globals, [])