diff --git a/packages/ts-html-plugin/test/util/lang-server.ts b/packages/ts-html-plugin/test/util/lang-server.ts index 46187e176..591e77aa1 100644 --- a/packages/ts-html-plugin/test/util/lang-server.ts +++ b/packages/ts-html-plugin/test/util/lang-server.ts @@ -64,6 +64,7 @@ export class TSLangServer { isClosed = false; server: ChildProcess; sequence = 0; + buffer = ''; // Add buffer for incomplete messages constructor( projectPath: string, @@ -86,32 +87,58 @@ export class TSLangServer { this.server.stdout?.on('data', (data) => { try { - const obj = JSON.parse(data.split('\n', 3)[2]); - - if (this.debug) { - console.dir(obj, { depth: 10 }); - } - - if (obj.success === false) { - this.errorEmitter.emit(obj.type === 'event' ? obj.event : obj.command, obj); - - // Error is fatal, close the server - if (!this.isClosed) { - this.isClosed = true; - this.server.stdin?.end(); - } - } else if (obj.type === 'event') { - this.responseEventEmitter.emit(obj.event, obj); - } else if (obj.type === 'response') { - this.responseCommandEmitter.emit(obj.command, obj); - } + this.buffer += data; + this.#processMessages(); } catch (error) { - console.error(data); + console.error(this.buffer); this.exitPromise.reject(error); } }); } + #processMessages() { + // Process all complete messages in the buffer + let headerMatch = this.buffer.match(/Content-Length: (\d+)\r?\n\r?\n/); + + while (headerMatch && headerMatch.index !== undefined) { + // TSServer protocol: Content-Length: N\r\n\r\n{JSON}\r\n + const contentLength = parseInt(headerMatch[1], 10); + const headerEnd = headerMatch.index + headerMatch[0].length; + const messageEnd = headerEnd + contentLength; + + // Check if we have the complete message + if (this.buffer.length < messageEnd) break; + + // Extract and parse the message + const jsonStr = this.buffer.substring(headerEnd, messageEnd); + const obj = JSON.parse(jsonStr); + + // Remove the processed message from buffer + this.buffer = this.buffer.substring(messageEnd); + + if (this.debug) { + console.dir(obj, { depth: 10 }); + } + + if (obj.success === false) { + this.errorEmitter.emit(obj.type === 'event' ? obj.event : obj.command, obj); + + // Error is fatal, close the server + if (!this.isClosed) { + this.isClosed = true; + this.server.stdin?.end(); + } + } else if (obj.type === 'event') { + this.responseEventEmitter.emit(obj.event, obj); + } else if (obj.type === 'response') { + this.responseCommandEmitter.emit(obj.command, obj); + } + + // Check for next message + headerMatch = this.buffer.match(/Content-Length: (\d+)\r?\n\r?\n/); + } + } + /** Opens the project, sends diagnostics request and returns the response */ async openWithDiagnostics(content: TemplateStringsArray, ...args: any[]) { const fileContent = `${TEST_HELPERS}\n${String.raw(content, ...args).trim()}`;