UNPKG

eslint_d

Version:

Speed up eslint to accelerate your development workflow

188 lines (152 loc) 6.19 kB
import net from 'node:net'; import { PassThrough } from 'node:stream'; import fs from 'node:fs/promises'; import supportsColor from 'supports-color'; import { assert, refute, match, sinon } from '@sinonjs/referee-sinon'; import { forwardToDaemon } from './forwarder.js'; import { createResolver } from './resolver.js'; describe('lib/forwarder', () => { const resolver = createResolver(); if (resolver === 'fail' || resolver === 'ignore') { throw new Error('Failed to create resolver'); } const config = { token: 'token', port: 123, pid: 456, hash: 'hash' }; let socket; let argv; function returnThis() { // @ts-ignore return this; } beforeEach(() => { socket = new PassThrough(); sinon.replace(socket, 'write', sinon.fake()); sinon.replace(socket, 'end', sinon.fake()); sinon.replace(socket, 'on', sinon.fake(returnThis)); sinon.replace(net, 'connect', sinon.fake.returns(socket)); sinon.replace(console, 'error', sinon.fake()); argv = [process.argv0, 'eslint_d']; sinon.replace(process, 'argv', argv); }); context('forwardToDaemon', () => { it('connects to 127.0.0.1 on port from config', () => { forwardToDaemon(resolver, config); assert.calledOnceWith(net.connect, config.port, '127.0.0.1'); }); it('writes token, color level, cwd and argv to socket', () => { sinon.replace(process, 'cwd', sinon.fake.returns('the/cwd')); forwardToDaemon(resolver, config); assert.calledOnceWith( socket.write, `token ${supportsColor.stdout?.['level'] || 0} "the/cwd" ["node","eslint_d"]` ); assert.calledOnce(socket.end); }); it('does not read from stdin by default', () => { sinon.replace(process.stdin, 'on', sinon.fake(returnThis)); forwardToDaemon(resolver, config); refute.called(process.stdin.on); }); it('reads from stdin if --stdin is in argv', () => { sinon.replace(process.stdin, 'on', sinon.fake(returnThis)); argv.push('--stdin'); forwardToDaemon(resolver, config); assert.calledThrice(process.stdin.on); assert.calledWith(process.stdin.on, 'readable', match.func); assert.calledWith(process.stdin.on, 'end', match.func); assert.calledWith(process.stdin.on, 'error', match.func); }); it('writes text from stdin to socket', async () => { const text = 'text from stdin'; const stdin = new PassThrough(); argv.push('--stdin'); sinon.replaceGetter(process, 'stdin', () => stdin); forwardToDaemon(resolver, config); stdin.end(text); await new Promise(setImmediate); assert.calledThrice(socket.write); assert.calledWith(socket.write, '\n'); assert.calledWith(socket.write, text); }); it('forwards socket response to stdout, except for the last 5 characters', () => { const chunks = ['response ', 'from daemon']; sinon.replace( socket, 'read', sinon.fake(() => (chunks.length ? chunks.shift() : null)) ); sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); socket.on.firstCall.callback(); // readable assert.calledTwice(process.stdout.write); assert.calledWith(process.stdout.write, 'resp'); assert.calledWith(process.stdout.write, 'onse from d'); }); it('handles EXIT0 from response', () => { const chunks = ['response from daemonEXIT0']; sinon.replace( socket, 'read', sinon.fake(() => (chunks.length ? chunks.shift() : null)) ); sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); socket.on.firstCall.callback(); // readable socket.on.secondCall.callback(); // end assert.calledOnceWith(process.stdout.write, 'response from daemon'); assert.equals(process.exitCode, 0); refute.called(console.error); }); it('handles EXIT1 from response', () => { const chunks = ['response from daemonEXIT1']; sinon.replace( socket, 'read', sinon.fake(() => (chunks.length ? chunks.shift() : null)) ); sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); socket.on.firstCall.callback(); // readable socket.on.secondCall.callback(); // end assert.calledWith(process.stdout.write, 'response from daemon'); assert.equals(process.exitCode, 1); refute.called(console.error); }); it('logs error and sets exitCode to 1 if response does not end with EXIT marker', () => { const chunks = ['response from daemon']; sinon.replace( socket, 'read', sinon.fake(() => (chunks.length ? chunks.shift() : null)) ); sinon.replace(process.stdout, 'write', sinon.fake()); forwardToDaemon(resolver, config); socket.on.firstCall.callback(); // readable socket.on.secondCall.callback(); // end assert.calledWith(process.stdout.write, 'response from d'); assert.calledWith(process.stdout.write, 'aemon'); assert.equals(process.exitCode, 1); assert.calledOnceWith(console.error, 'eslint_d: unexpected response'); }); it('logs error from stream', () => { sinon.replace(fs, 'unlink', sinon.fake.resolves()); forwardToDaemon(resolver, config); socket.on.thirdCall.callback(new Error('Ouch!')); // error assert.calledOnceWith(console.error, 'eslint_d: Error: Ouch!'); assert.equals(process.exitCode, 1); refute.called(fs.unlink); }); it('logs ECONNREFUSED error from stream and removes config', () => { sinon.replace(fs, 'unlink', sinon.fake.resolves()); forwardToDaemon(resolver, config); const err = new Error('Connection refused'); err['code'] = 'ECONNREFUSED'; socket.on.thirdCall.callback(err); // error assert.calledOnceWith( console.error, 'eslint_d: Error: Connection refused - removing config' ); assert.equals(process.exitCode, 1); assert.calledOnceWith(fs.unlink, `${resolver.base}/.eslint_d`); }); }); });