The current integration test for language service involves piping the results of one process to another using Unix pipes. This makes the test hard to debug, and hard to configure. This commit refactors the integration test to use regular Jasmine scaffolding. More importantly, it tests the way the language service will actually be installed by end users. Users would not have to add `@angular/language-service` to the plugins section in tsconfig.json Instead, all they need to do is install the *extension* from the VS Code Marketplace and Angular Language Service will be loaded as a global plugin. PR Close #28168
		
			
				
	
	
		
			71 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			71 lines
		
	
	
		
			2.0 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;
 | |
|       const CONTENT_LENGTH = 'Content-Length: '
 | |
|       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 === "event") {
 | |
|           return;
 | |
|         }
 | |
|         this.responseEmitter.emit('response', payload);
 | |
|       }
 | |
|       catch (error) {
 | |
|         this.responseEmitter.emit('error', error);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   async send(type: string, command: string, params: {}) {
 | |
|     const request = {
 | |
|       seq: this.id++,
 | |
|       type,
 | |
|       command,
 | |
|       arguments: params
 | |
|     };
 | |
|     this.server.stdin.write(JSON.stringify(request) + '\r\n');
 | |
|     return new Promise((resolve, reject) => {
 | |
|       this.responseEmitter.once('response', resolve);
 | |
|       this.responseEmitter.once('error', reject);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   async sendRequest(command: string, params: {}) {
 | |
|     return this.send('request', command, params);
 | |
|   }
 | |
| }
 |