diff --git a/lib/forwarder.js b/lib/forwarder.js index 72dc19f..e631de5 100644 --- a/lib/forwarder.js +++ b/lib/forwarder.js @@ -24,15 +24,35 @@ const DAEMON_TERMINATION_CODE_REGEXP = new RegExp( * @param {Config} config */ export async function forwardToDaemon(resolver, config) { + const eslint_args = process.argv.slice(); const text = process.argv.includes('--stdin') ? await readStdin() : null; const { stdout } = supportsColor; + const fix_to_stdout_index = eslint_args.indexOf('--fix-to-stdout'); + const fix_to_stdout = fix_to_stdout_index !== -1; + + if (fix_to_stdout) { + if (!eslint_args.includes('--stdin')) { + console.error('--fix-to-stdout requires passing --stdin as well'); + // eslint-disable-next-line require-atomic-updates -- not quite sure which execution flow could be problematic here + process.exitCode = 1; + return; + } + eslint_args.splice( + fix_to_stdout_index, + 1, + '--fix-dry-run', + '--format', + 'json' + ); + } + const socket = net.connect(config.port, '127.0.0.1'); const args = [ config.token, stdout ? stdout.level : 0, JSON.stringify(process.cwd()), - JSON.stringify(process.argv) + JSON.stringify(eslint_args) ]; socket.write(args.join(' ')); if (text) { @@ -48,7 +68,10 @@ export async function forwardToDaemon(resolver, config) { let chunk = ''; while ((chunk = socket.read()) !== null) { content += chunk; - if (content.length > DAEMON_TERMINATION_CODE_MAX_LENGTH) { + if ( + !fix_to_stdout && + content.length > DAEMON_TERMINATION_CODE_MAX_LENGTH + ) { const message_length = content.length - DAEMON_TERMINATION_CODE_MAX_LENGTH; // write everything we are sure doesn't contain the termination code: @@ -78,6 +101,11 @@ export async function forwardToDaemon(resolver, config) { process.exitCode = Number(match[1]); + if (fix_to_stdout) { + const object = JSON.parse(content); + content = object[0].output || text; + } + if (content) { process.stdout.write(content); } diff --git a/lib/forwarder.test.js b/lib/forwarder.test.js index 7cf4b6e..ee8bd01 100644 --- a/lib/forwarder.test.js +++ b/lib/forwarder.test.js @@ -225,5 +225,19 @@ describe('lib/forwarder', () => { assert.equals(process.exitCode, 1); assert.calledOnceWith(fs.unlink, `${resolver.base}/.eslint_d`); }); + + context('--fix-to-stdout', () => { + it('throws if --stdin is absent', async () => { + argv.push('--fix-to-stdout'); + + await forwardToDaemon(resolver, config); + + assert.equals(process.exitCode, 1); + assert.calledOnceWith( + console.error, + '--fix-to-stdout requires passing --stdin as well' + ); + }); + }); }); }); diff --git a/test/test.integration.js b/test/test.integration.js index da1bf98..5b312f3 100644 --- a/test/test.integration.js +++ b/test/test.integration.js @@ -13,7 +13,8 @@ const SUPPORTED_ESLINT_VERSIONS = [ 'v9.0.x' ]; -describe('integration tests', () => { +describe('integration tests', function() { + this.timeout(0); const eslint_d = path.resolve('bin/eslint_d.js'); const require = createRequire(import.meta.url); const { version } = require('../package.json'); @@ -33,7 +34,7 @@ describe('integration tests', () => { return new Promise((resolve) => { const child = child_process.exec( `${bin} ${args}`, - { cwd }, + { cwd, env: { ...process.env, NODE_OPTIONS: '--inspect-brk=9229' } }, (error, stdout, stderr) => resolve({ error, stdout, stderr }) ); if (stdin && child.stdin) { @@ -67,7 +68,7 @@ describe('integration tests', () => { }); }); - [SUPPORTED_ESLINT_VERSIONS].forEach((fixture) => { + SUPPORTED_ESLINT_VERSIONS.forEach((fixture) => { context(fixture, () => { const cwd = path.resolve(`test/fixture/${fixture}`); const { version: eslint_version } = require( @@ -176,4 +177,99 @@ describe('integration tests', () => { }); }); }); + + context('--fix-to-stdout', () => { + SUPPORTED_ESLINT_VERSIONS.forEach((fixture) => { + const cwd = path.resolve(`test/fixture/${fixture}`); + const config = `${cwd}/node_modules/eslint/.eslint_d`; + + beforeEach(async () => { + await killDaemon(); + await startDaemon(); + }); + + afterEach(killDaemon); + + const run_args = `--fix-to-stdout --stdin --stdin-filename ${cwd}/../foo.js`; + + context('when file only contains fixable problems', () => { + it.only('prints input if no change is needed', async () => { + const stdin = `console.log('Hello eslint');`; + const { error, stdout, stderr } = await run(run_args, { + cwd, + stdin + }); + + assert.equals(stdout, stdin); + assert.equals(stderr, ''); + assert.isNull(error); + }); + + it('prints fixed output if change is needed', async () => { + const { error, stdout, stderr } = await run(run_args, { + cwd, + stdin: `console.log("Hello eslint");` + }); + + assert.equals(stdout, `console.log('Hello eslint');`); + assert.equals(stderr, ''); + assert.isNull(error); + }); + }); + + context('when file contains non-fixable problems', () => { + it('prints input if no change is needed', async () => { + const stdin = `/* eslint symbol-description: "error" */ + console.log('Hello' + Symbol())`; + + const { error, stdout, stderr } = await run(run_args, { + cwd, + stdin + }); + + assert.equals( + stdout, + `/* eslint symbol-description: "error" */ + console.log('Hello' + Symbol())` + ); + assert.equals(stderr, ''); + refute.isNull(error); + assert.equals(error?.['code'], 1); + }); + + it('prints fixed output if change is needed', async () => { + const stdin = `/* eslint symbol-description: "error" */ + console.log("Hello" + Symbol())`; + + const { error, stdout, stderr } = await run(run_args, { + cwd, + stdin + }); + + assert.equals( + stdout, + `/* eslint symbol-description: "error" */ + console.log('Hello' + Symbol())` + ); + assert.equals(stderr, ''); + refute.isNull(error); + assert.equals(error?.['code'], 1); + }); + }); + + async function killDaemon() { + try { + const raw = await fs.readFile(config, 'utf8'); + const [, , pid] = raw.split(' '); + process.kill(Number(pid), 0); + } catch { + // nothing to kill + } + } + + async function startDaemon() { + await run('start', { cwd }); + } + }); + }); });