Skip to content

Commit 6d7045e

Browse files
committed
Merge pull request #2041 from Microsoft/TSServer
TS Server
2 parents d2c992c + d364f61 commit 6d7045e

35 files changed

+5149
-27
lines changed

Jakefile

+28-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ var child_process = require("child_process");
88
// Variables
99
var compilerDirectory = "src/compiler/";
1010
var servicesDirectory = "src/services/";
11+
var serverDirectory = "src/server/";
1112
var harnessDirectory = "src/harness/";
1213
var libraryDirectory = "src/lib/";
1314
var scriptsDirectory = "scripts/";
@@ -90,6 +91,16 @@ var servicesSources = [
9091
return path.join(servicesDirectory, f);
9192
}));
9293

94+
var serverSources = [
95+
"node.d.ts",
96+
"editorServices.ts",
97+
"protocol.d.ts",
98+
"session.ts",
99+
"server.ts"
100+
].map(function (f) {
101+
return path.join(serverDirectory, f);
102+
});
103+
93104
var definitionsRoots = [
94105
"compiler/types.d.ts",
95106
"compiler/scanner.d.ts",
@@ -130,6 +141,13 @@ var harnessSources = [
130141
"services/preProcessFile.ts"
131142
].map(function (f) {
132143
return path.join(unittestsDirectory, f);
144+
})).concat([
145+
"protocol.d.ts",
146+
"session.ts",
147+
"client.ts",
148+
"editorServices.ts",
149+
].map(function (f) {
150+
return path.join(serverDirectory, f);
133151
}));
134152

135153
var librarySourceMap = [
@@ -327,6 +345,7 @@ var tscFile = path.join(builtLocalDirectory, compilerFilename);
327345
compileFile(tscFile, compilerSources, [builtLocalDirectory, copyright].concat(compilerSources), [copyright], /*useBuiltCompiler:*/ false);
328346

329347
var servicesFile = path.join(builtLocalDirectory, "typescriptServices.js");
348+
var nodePackageFile = path.join(builtLocalDirectory, "typescript.js");
330349
compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].concat(servicesSources),
331350
/*prefixes*/ [copyright],
332351
/*useBuiltCompiler*/ true,
@@ -336,7 +355,10 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca
336355
/*preserveConstEnums*/ true,
337356
/*keepComments*/ false,
338357
/*noResolve*/ false,
339-
/*stripInternal*/ false);
358+
/*stripInternal*/ false,
359+
/*callback*/ function () {
360+
jake.cpR(servicesFile, nodePackageFile, {silent: true});
361+
});
340362

341363
var nodeDefinitionsFile = path.join(builtLocalDirectory, "typescript.d.ts");
342364
var standaloneDefinitionsFile = path.join(builtLocalDirectory, "typescriptServices.d.ts");
@@ -378,9 +400,12 @@ compileFile(nodeDefinitionsFile, servicesSources,[builtLocalDirectory, copyright
378400
jake.rmRf(tempDirPath, {silent: true});
379401
});
380402

403+
var serverFile = path.join(builtLocalDirectory, "tsserver.js");
404+
compileFile(serverFile, serverSources,[builtLocalDirectory, copyright].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true);
405+
381406
// Local target to build the compiler and services
382407
desc("Builds the full compiler and services");
383-
task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile]);
408+
task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile]);
384409

385410
// Local target to build only tsc.js
386411
desc("Builds only the compiler");
@@ -435,7 +460,7 @@ task("generate-spec", [specMd])
435460
// Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory
436461
desc("Makes a new LKG out of the built js files");
437462
task("LKG", ["clean", "release", "local"].concat(libraryTargets), function() {
438-
var expectedFiles = [tscFile, servicesFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets);
463+
var expectedFiles = [tscFile, servicesFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets);
439464
var missingFiles = expectedFiles.filter(function (f) {
440465
return !fs.existsSync(f);
441466
});

bin/tsserver

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
require('./tsserver.js')

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
"url": "https://github.com/Microsoft/TypeScript.git"
2626
},
2727
"preferGlobal": true,
28-
"main": "./bin/typescriptServices.js",
28+
"main": "./bin/typescript.js",
2929
"bin": {
30-
"tsc": "./bin/tsc"
30+
"tsc": "./bin/tsc",
31+
"tsserver": "./bin/tsserver"
3132
},
3233
"engines": {
3334
"node": ">=0.8.0"

src/harness/fourslash.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ module FourSlash {
282282
return new Harness.LanguageService.NativeLanugageServiceAdapter(cancellationToken, compilationOptions);
283283
case FourSlashTestType.Shims:
284284
return new Harness.LanguageService.ShimLanugageServiceAdapter(cancellationToken, compilationOptions);
285+
case FourSlashTestType.Server:
286+
return new Harness.LanguageService.ServerLanugageServiceAdapter(cancellationToken, compilationOptions);
285287
default:
286288
throw new Error("Unknown FourSlash test type: ");
287289
}
@@ -418,6 +420,9 @@ module FourSlash {
418420
this.activeFile = fileToOpen;
419421
var fileName = fileToOpen.fileName.replace(Harness.IO.directoryName(fileToOpen.fileName), '').substr(1);
420422
this.scenarioActions.push('<OpenFile FileName="" SrcFileId="' + fileName + '" FileId="' + fileName + '" />');
423+
424+
// Let the host know that this file is now open
425+
this.languageServiceAdapterHost.openFile(fileToOpen.fileName);
421426
}
422427

423428
public verifyErrorExistsBetweenMarkers(startMarkerName: string, endMarkerName: string, negative: boolean) {
@@ -1927,7 +1932,7 @@ module FourSlash {
19271932
}
19281933

19291934
var missingItem = { name: name, kind: kind };
1930-
this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')');
1935+
this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items, null, " ") + ')');
19311936
}
19321937

19331938
private navigationBarItemsContains(items: ts.NavigationBarItem[], name: string, kind: string) {

src/harness/fourslashRunner.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
const enum FourSlashTestType {
66
Native,
7-
Shims
7+
Shims,
8+
Server
89
}
910

1011
class FourSlashRunner extends RunnerBase {
@@ -22,6 +23,10 @@ class FourSlashRunner extends RunnerBase {
2223
this.basePath = 'tests/cases/fourslash/shims';
2324
this.testSuiteName = 'fourslash-shims';
2425
break;
26+
case FourSlashTestType.Server:
27+
this.basePath = 'tests/cases/fourslash/server';
28+
this.testSuiteName = 'fourslash-server';
29+
break;
2530
}
2631
}
2732

src/harness/harness.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616

1717
/// <reference path='..\services\services.ts' />
1818
/// <reference path='..\services\shims.ts' />
19+
/// <reference path='..\server\session.ts' />
20+
/// <reference path='..\server\client.ts' />
21+
/// <reference path='..\server\node.d.ts' />
1922
/// <reference path='external\mocha.d.ts'/>
2023
/// <reference path='external\chai.d.ts'/>
2124
/// <reference path='sourceMapRecorder.ts'/>
2225
/// <reference path='runnerbase.ts'/>
2326

24-
declare var require: any;
25-
declare var process: any;
26-
var Buffer = require('buffer').Buffer;
27+
var Buffer: BufferConstructor = require('buffer').Buffer;
2728

2829
// this will work in the browser via browserify
2930
var _chai: typeof chai = require('chai');

src/harness/harnessLanguageService.ts

+161-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference path='..\services\services.ts' />
22
/// <reference path='..\services\shims.ts' />
3+
/// <reference path='..\server\client.ts' />
34
/// <reference path='harness.ts' />
45

56
module Harness.LanguageService {
@@ -23,18 +24,18 @@ module Harness.LanguageService {
2324
this.version++;
2425
}
2526

26-
public editContent(minChar: number, limChar: number, newText: string): void {
27+
public editContent(start: number, end: number, newText: string): void {
2728
// Apply edits
28-
var prefix = this.content.substring(0, minChar);
29+
var prefix = this.content.substring(0, start);
2930
var middle = newText;
30-
var suffix = this.content.substring(limChar);
31+
var suffix = this.content.substring(end);
3132
this.setContent(prefix + middle + suffix);
3233

3334
// Store edit range + new length of script
3435
this.editRanges.push({
3536
length: this.content.length,
3637
textChangeRange: ts.createTextChangeRange(
37-
ts.createTextSpanFromBounds(minChar, limChar), newText.length)
38+
ts.createTextSpanFromBounds(start, end), newText.length)
3839
});
3940

4041
// Update version #
@@ -145,24 +146,17 @@ module Harness.LanguageService {
145146
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
146147
}
147148

148-
public updateScript(fileName: string, content: string) {
149+
public editScript(fileName: string, start: number, end: number, newText: string) {
149150
var script = this.getScriptInfo(fileName);
150151
if (script !== null) {
151-
script.updateContent(content);
152+
script.editContent(start, end, newText);
152153
return;
153154
}
154155

155-
this.addScript(fileName, content);
156+
throw new Error("No script with name '" + fileName + "'");
156157
}
157158

158-
public editScript(fileName: string, minChar: number, limChar: number, newText: string) {
159-
var script = this.getScriptInfo(fileName);
160-
if (script !== null) {
161-
script.editContent(minChar, limChar, newText);
162-
return;
163-
}
164-
165-
throw new Error("No script with name '" + fileName + "'");
159+
public openFile(fileName: string): void {
166160
}
167161

168162
/**
@@ -236,8 +230,7 @@ module Harness.LanguageService {
236230
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
237231
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
238232
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
239-
updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); }
240-
editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); }
233+
editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); }
241234
lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); }
242235
positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); }
243236

@@ -442,5 +435,156 @@ module Harness.LanguageService {
442435
return convertResult;
443436
}
444437
}
438+
439+
// Server adapter
440+
class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost {
441+
private client: ts.server.SessionClient;
442+
443+
constructor(cancellationToken: ts.CancellationToken, settings: ts.CompilerOptions) {
444+
super(cancellationToken, settings);
445+
}
446+
447+
onMessage(message: string): void {
448+
449+
}
450+
451+
writeMessage(message: string): void {
452+
453+
}
454+
455+
setClient(client: ts.server.SessionClient) {
456+
this.client = client;
457+
}
458+
459+
openFile(fileName: string): void {
460+
super.openFile(fileName);
461+
this.client.openFile(fileName);
462+
}
463+
464+
editScript(fileName: string, start: number, end: number, newText: string) {
465+
super.editScript(fileName, start, end, newText);
466+
this.client.changeFile(fileName, start, end, newText);
467+
}
468+
}
469+
470+
class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
471+
args: string[] = [];
472+
newLine: string;
473+
useCaseSensitiveFileNames: boolean = false;
474+
475+
constructor(private host: NativeLanguageServiceHost) {
476+
this.newLine = this.host.getNewLine();
477+
}
478+
479+
onMessage(message: string): void {
480+
481+
}
482+
483+
writeMessage(message: string): void {
484+
}
485+
486+
write(message: string): void {
487+
this.writeMessage(message);
488+
}
489+
490+
readFile(fileName: string): string {
491+
if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) {
492+
fileName = Harness.Compiler.defaultLibFileName;
493+
}
494+
495+
var snapshot = this.host.getScriptSnapshot(fileName);
496+
return snapshot && snapshot.getText(0, snapshot.getLength());
497+
}
498+
499+
writeFile(name: string, text: string, writeByteOrderMark: boolean): void {
500+
}
501+
502+
resolvePath(path: string): string {
503+
return path;
504+
}
505+
506+
fileExists(path: string): boolean {
507+
return !!this.host.getScriptSnapshot(path);
508+
}
509+
510+
directoryExists(path: string): boolean {
511+
return false;
512+
}
513+
514+
getExecutingFilePath(): string {
515+
return "";
516+
}
517+
518+
exit(exitCode: number): void {
519+
}
520+
521+
createDirectory(directoryName: string): void {
522+
throw new Error("Not Implemented Yet.");
523+
}
524+
525+
getCurrentDirectory(): string {
526+
return this.host.getCurrentDirectory();
527+
}
528+
529+
readDirectory(path: string, extension?: string): string[] {
530+
throw new Error("Not implemented Yet.");
531+
}
532+
533+
watchFile(fileName: string, callback: (fileName: string) => void): ts.FileWatcher {
534+
return { close() { } };
535+
}
536+
537+
close(): void {
538+
}
539+
540+
info(message: string): void {
541+
return this.host.log(message);
542+
}
543+
544+
msg(message: string) {
545+
return this.host.log(message);
546+
}
547+
548+
endGroup(): void {
549+
}
550+
551+
perftrc(message: string): void {
552+
return this.host.log(message);
553+
}
554+
555+
startGroup(): void {
556+
}
557+
}
558+
559+
export class ServerLanugageServiceAdapter implements LanguageServiceAdapter {
560+
private host: SessionClientHost;
561+
private client: ts.server.SessionClient;
562+
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
563+
// This is the main host that tests use to direct tests
564+
var clientHost = new SessionClientHost(cancellationToken, options);
565+
var client = new ts.server.SessionClient(clientHost);
566+
567+
// This host is just a proxy for the clientHost, it uses the client
568+
// host to answer server queries about files on disk
569+
var serverHost = new SessionServerHost(clientHost);
570+
var server = new ts.server.Session(serverHost, serverHost);
571+
572+
// Fake the connection between the client and the server
573+
serverHost.writeMessage = client.onMessage.bind(client);
574+
clientHost.writeMessage = server.onMessage.bind(server);
575+
576+
// Wire the client to the host to get notifications when a file is open
577+
// or edited.
578+
clientHost.setClient(client);
579+
580+
// Set the properties
581+
this.client = client;
582+
this.host = clientHost;
583+
}
584+
getHost() { return this.host; }
585+
getLanguageService(): ts.LanguageService { return this.client; }
586+
getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
587+
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
588+
}
445589
}
446590

0 commit comments

Comments
 (0)