BREAKING CHANGE: typescript 3.4 and 3.5 are no longer supported, please update to typescript 3.6 Fixes #32380 PR Close #32946
		
			
				
	
	
		
			76 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			76 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { ChildProcess } from "child_process";
 | 
						|
import { EventEmitter } from "events";
 | 
						|
 | 
						|
/**
 | 
						|
 * Provides a client for tsserver. Tsserver does not use standard JSON-RPC
 | 
						|
 * protocol thus the need for this custom client.
 | 
						|
 */
 | 
						|
export class Client {
 | 
						|
  private data: Buffer|undefined;
 | 
						|
  private id = 0;
 | 
						|
  private responseEmitter = new EventEmitter();
 | 
						|
 | 
						|
  constructor(private readonly server: ChildProcess) {}
 | 
						|
 | 
						|
  listen() {
 | 
						|
    this.server.stdout!.on('data', (data: Buffer) => {
 | 
						|
      this.data = this.data ? Buffer.concat([this.data, data]) : data;
 | 
						|
      // tsserver could batch multiple responses together so we have to go
 | 
						|
      // through the entire buffer to keep looking for messages.
 | 
						|
      const CONTENT_LENGTH = 'Content-Length: '
 | 
						|
      do {
 | 
						|
        const index = this.data.indexOf(CONTENT_LENGTH);
 | 
						|
        if (index < 0) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        let start = index + CONTENT_LENGTH.length;
 | 
						|
        let end = this.data.indexOf('\r\n', start);
 | 
						|
        if (end < start) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        const contentLengthStr = this.data.slice(start, end).toString();
 | 
						|
        const contentLength = Number(contentLengthStr);
 | 
						|
        if (isNaN(contentLength) || contentLength < 0) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        start = end + 4;
 | 
						|
        end = start + contentLength;
 | 
						|
        if (end > this.data.length) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        const content = this.data.slice(start, end).toString();
 | 
						|
        this.data = this.data.slice(end);
 | 
						|
        try {
 | 
						|
          const payload = JSON.parse(content);
 | 
						|
          if (payload.type === "response") {
 | 
						|
            const seq = `${payload.request_seq}`;
 | 
						|
            this.responseEmitter.emit(seq, payload);
 | 
						|
          }
 | 
						|
        }
 | 
						|
        catch (error) {
 | 
						|
          this.responseEmitter.emit('error', error);
 | 
						|
        }
 | 
						|
      } while (this.data.length > 0)
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  async send(type: string, command: string, params: {}) {
 | 
						|
    const seq = this.id++;
 | 
						|
    const request = {
 | 
						|
      seq,
 | 
						|
      type,
 | 
						|
      command,
 | 
						|
      arguments: params
 | 
						|
    };
 | 
						|
    this.server.stdin!.write(JSON.stringify(request) + '\r\n');
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      this.responseEmitter.once(`${seq}`, resolve);
 | 
						|
      this.responseEmitter.once('error', reject);
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  async sendRequest(command: string, params: {}) {
 | 
						|
    return this.send('request', command, params);
 | 
						|
  }
 | 
						|
}
 |