diff --git a/packages/cli-repl/src/line-by-line-input.spec.ts b/packages/cli-repl/src/line-by-line-input.spec.ts index 1614e9c4b7..f6e595d23c 100644 --- a/packages/cli-repl/src/line-by-line-input.spec.ts +++ b/packages/cli-repl/src/line-by-line-input.spec.ts @@ -63,4 +63,22 @@ describe('LineByLineInput', () => { expect(forwardedChunks).to.deep.equal(['a', 'b', '\n']); }); }); + + context('when a data listener calls nextLine() itself after Ctrl+C', () => { + it('does not emit data while already emitting data', () => { + let dataCalls = 0; + let insideDataCalls = 0; + lineByLineInput.on('data', () => { + expect(insideDataCalls).to.equal(0); + insideDataCalls++; + if (dataCalls++ === 0) { + lineByLineInput.nextLine(); + } + insideDataCalls--; + }); + stdinMock.emit('data', Buffer.from('foo\n\u0003')); + expect(dataCalls).to.equal(5); + expect(forwardedChunks).to.deep.equal(['\u0003', 'f', 'o', 'o', '\n']); + }); + }); }); diff --git a/packages/cli-repl/src/line-by-line-input.ts b/packages/cli-repl/src/line-by-line-input.ts index aed8644dd0..c9982133c9 100644 --- a/packages/cli-repl/src/line-by-line-input.ts +++ b/packages/cli-repl/src/line-by-line-input.ts @@ -27,6 +27,7 @@ export class LineByLineInput extends Readable { private _blockOnNewLineEnabled: boolean; private _charQueue: (string | null)[]; private _decoder: StringDecoder; + private _insidePushCalls: number; constructor(readable: NodeJS.ReadStream) { super(); @@ -35,6 +36,7 @@ export class LineByLineInput extends Readable { this._blockOnNewLineEnabled = true; this._charQueue = []; this._decoder = new StringDecoder('utf-8'); + this._insidePushCalls = 0; const isReadableEvent = (name: string) => name === 'readable' || name === 'data' || name === 'end' || name === 'keypress'; @@ -144,7 +146,11 @@ export class LineByLineInput extends Readable { // have in the buffer if in the meanwhile something // downstream explicitly called pause(), as that may cause // unexpected behaviors. - !this._originalInput.isPaused() + !this._originalInput.isPaused() && + + // If we are already inside a push() call, then we do not need to flush + // the queue again. + this._insidePushCalls === 0 ) { const char = this._charQueue.shift() as string | null; @@ -167,4 +173,13 @@ export class LineByLineInput extends Readable { private _isCtrlC(char: string | null): boolean { return char === CTRL_C; } + + push(chunk: Buffer | string | null): boolean { + this._insidePushCalls++; + try { + return super.push(chunk); + } finally { + this._insidePushCalls--; + } + } }