Skip to content

Commit

Permalink
Merge pull request #2041 from Microsoft/TSServer
Browse files Browse the repository at this point in the history
TS Server
  • Loading branch information
steveluc committed Feb 19, 2015
2 parents d2c992c + d364f61 commit 6d7045e
Show file tree
Hide file tree
Showing 35 changed files with 5,149 additions and 27 deletions.
31 changes: 28 additions & 3 deletions Jakefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var child_process = require("child_process");
// Variables
var compilerDirectory = "src/compiler/";
var servicesDirectory = "src/services/";
var serverDirectory = "src/server/";
var harnessDirectory = "src/harness/";
var libraryDirectory = "src/lib/";
var scriptsDirectory = "scripts/";
Expand Down Expand Up @@ -90,6 +91,16 @@ var servicesSources = [
return path.join(servicesDirectory, f);
}));

var serverSources = [
"node.d.ts",
"editorServices.ts",
"protocol.d.ts",
"session.ts",
"server.ts"
].map(function (f) {
return path.join(serverDirectory, f);
});

var definitionsRoots = [
"compiler/types.d.ts",
"compiler/scanner.d.ts",
Expand Down Expand Up @@ -130,6 +141,13 @@ var harnessSources = [
"services/preProcessFile.ts"
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
"protocol.d.ts",
"session.ts",
"client.ts",
"editorServices.ts",
].map(function (f) {
return path.join(serverDirectory, f);
}));

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

var servicesFile = path.join(builtLocalDirectory, "typescriptServices.js");
var nodePackageFile = path.join(builtLocalDirectory, "typescript.js");
compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].concat(servicesSources),
/*prefixes*/ [copyright],
/*useBuiltCompiler*/ true,
Expand All @@ -336,7 +355,10 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca
/*preserveConstEnums*/ true,
/*keepComments*/ false,
/*noResolve*/ false,
/*stripInternal*/ false);
/*stripInternal*/ false,
/*callback*/ function () {
jake.cpR(servicesFile, nodePackageFile, {silent: true});
});

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

var serverFile = path.join(builtLocalDirectory, "tsserver.js");
compileFile(serverFile, serverSources,[builtLocalDirectory, copyright].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true);

// Local target to build the compiler and services
desc("Builds the full compiler and services");
task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile]);
task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile]);

// Local target to build only tsc.js
desc("Builds only the compiler");
Expand Down Expand Up @@ -435,7 +460,7 @@ task("generate-spec", [specMd])
// Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory
desc("Makes a new LKG out of the built js files");
task("LKG", ["clean", "release", "local"].concat(libraryTargets), function() {
var expectedFiles = [tscFile, servicesFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets);
var expectedFiles = [tscFile, servicesFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets);
var missingFiles = expectedFiles.filter(function (f) {
return !fs.existsSync(f);
});
Expand Down
2 changes: 2 additions & 0 deletions bin/tsserver
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('./tsserver.js')
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
"url": "https://github.com/Microsoft/TypeScript.git"
},
"preferGlobal": true,
"main": "./bin/typescriptServices.js",
"main": "./bin/typescript.js",
"bin": {
"tsc": "./bin/tsc"
"tsc": "./bin/tsc",
"tsserver": "./bin/tsserver"
},
"engines": {
"node": ">=0.8.0"
Expand Down
7 changes: 6 additions & 1 deletion src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ module FourSlash {
return new Harness.LanguageService.NativeLanugageServiceAdapter(cancellationToken, compilationOptions);
case FourSlashTestType.Shims:
return new Harness.LanguageService.ShimLanugageServiceAdapter(cancellationToken, compilationOptions);
case FourSlashTestType.Server:
return new Harness.LanguageService.ServerLanugageServiceAdapter(cancellationToken, compilationOptions);
default:
throw new Error("Unknown FourSlash test type: ");
}
Expand Down Expand Up @@ -418,6 +420,9 @@ module FourSlash {
this.activeFile = fileToOpen;
var fileName = fileToOpen.fileName.replace(Harness.IO.directoryName(fileToOpen.fileName), '').substr(1);
this.scenarioActions.push('<OpenFile FileName="" SrcFileId="' + fileName + '" FileId="' + fileName + '" />');

// Let the host know that this file is now open
this.languageServiceAdapterHost.openFile(fileToOpen.fileName);
}

public verifyErrorExistsBetweenMarkers(startMarkerName: string, endMarkerName: string, negative: boolean) {
Expand Down Expand Up @@ -1927,7 +1932,7 @@ module FourSlash {
}

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

private navigationBarItemsContains(items: ts.NavigationBarItem[], name: string, kind: string) {
Expand Down
7 changes: 6 additions & 1 deletion src/harness/fourslashRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

const enum FourSlashTestType {
Native,
Shims
Shims,
Server
}

class FourSlashRunner extends RunnerBase {
Expand All @@ -22,6 +23,10 @@ class FourSlashRunner extends RunnerBase {
this.basePath = 'tests/cases/fourslash/shims';
this.testSuiteName = 'fourslash-shims';
break;
case FourSlashTestType.Server:
this.basePath = 'tests/cases/fourslash/server';
this.testSuiteName = 'fourslash-server';
break;
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

/// <reference path='..\services\services.ts' />
/// <reference path='..\services\shims.ts' />
/// <reference path='..\server\session.ts' />
/// <reference path='..\server\client.ts' />
/// <reference path='..\server\node.d.ts' />
/// <reference path='external\mocha.d.ts'/>
/// <reference path='external\chai.d.ts'/>
/// <reference path='sourceMapRecorder.ts'/>
/// <reference path='runnerbase.ts'/>

declare var require: any;
declare var process: any;
var Buffer = require('buffer').Buffer;
var Buffer: BufferConstructor = require('buffer').Buffer;

// this will work in the browser via browserify
var _chai: typeof chai = require('chai');
Expand Down
178 changes: 161 additions & 17 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference path='..\services\services.ts' />
/// <reference path='..\services\shims.ts' />
/// <reference path='..\server\client.ts' />
/// <reference path='harness.ts' />

module Harness.LanguageService {
Expand All @@ -23,18 +24,18 @@ module Harness.LanguageService {
this.version++;
}

public editContent(minChar: number, limChar: number, newText: string): void {
public editContent(start: number, end: number, newText: string): void {
// Apply edits
var prefix = this.content.substring(0, minChar);
var prefix = this.content.substring(0, start);
var middle = newText;
var suffix = this.content.substring(limChar);
var suffix = this.content.substring(end);
this.setContent(prefix + middle + suffix);

// Store edit range + new length of script
this.editRanges.push({
length: this.content.length,
textChangeRange: ts.createTextChangeRange(
ts.createTextSpanFromBounds(minChar, limChar), newText.length)
ts.createTextSpanFromBounds(start, end), newText.length)
});

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

public updateScript(fileName: string, content: string) {
public editScript(fileName: string, start: number, end: number, newText: string) {
var script = this.getScriptInfo(fileName);
if (script !== null) {
script.updateContent(content);
script.editContent(start, end, newText);
return;
}

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

public editScript(fileName: string, minChar: number, limChar: number, newText: string) {
var script = this.getScriptInfo(fileName);
if (script !== null) {
script.editContent(minChar, limChar, newText);
return;
}

throw new Error("No script with name '" + fileName + "'");
public openFile(fileName: string): void {
}

/**
Expand Down Expand Up @@ -236,8 +230,7 @@ module Harness.LanguageService {
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); }
editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); }
editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); }
lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); }
positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); }

Expand Down Expand Up @@ -442,5 +435,156 @@ module Harness.LanguageService {
return convertResult;
}
}

// Server adapter
class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost {
private client: ts.server.SessionClient;

constructor(cancellationToken: ts.CancellationToken, settings: ts.CompilerOptions) {
super(cancellationToken, settings);
}

onMessage(message: string): void {

}

writeMessage(message: string): void {

}

setClient(client: ts.server.SessionClient) {
this.client = client;
}

openFile(fileName: string): void {
super.openFile(fileName);
this.client.openFile(fileName);
}

editScript(fileName: string, start: number, end: number, newText: string) {
super.editScript(fileName, start, end, newText);
this.client.changeFile(fileName, start, end, newText);
}
}

class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
args: string[] = [];
newLine: string;
useCaseSensitiveFileNames: boolean = false;

constructor(private host: NativeLanguageServiceHost) {
this.newLine = this.host.getNewLine();
}

onMessage(message: string): void {

}

writeMessage(message: string): void {
}

write(message: string): void {
this.writeMessage(message);
}

readFile(fileName: string): string {
if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) {
fileName = Harness.Compiler.defaultLibFileName;
}

var snapshot = this.host.getScriptSnapshot(fileName);
return snapshot && snapshot.getText(0, snapshot.getLength());
}

writeFile(name: string, text: string, writeByteOrderMark: boolean): void {
}

resolvePath(path: string): string {
return path;
}

fileExists(path: string): boolean {
return !!this.host.getScriptSnapshot(path);
}

directoryExists(path: string): boolean {
return false;
}

getExecutingFilePath(): string {
return "";
}

exit(exitCode: number): void {
}

createDirectory(directoryName: string): void {
throw new Error("Not Implemented Yet.");
}

getCurrentDirectory(): string {
return this.host.getCurrentDirectory();
}

readDirectory(path: string, extension?: string): string[] {
throw new Error("Not implemented Yet.");
}

watchFile(fileName: string, callback: (fileName: string) => void): ts.FileWatcher {
return { close() { } };
}

close(): void {
}

info(message: string): void {
return this.host.log(message);
}

msg(message: string) {
return this.host.log(message);
}

endGroup(): void {
}

perftrc(message: string): void {
return this.host.log(message);
}

startGroup(): void {
}
}

export class ServerLanugageServiceAdapter implements LanguageServiceAdapter {
private host: SessionClientHost;
private client: ts.server.SessionClient;
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
// This is the main host that tests use to direct tests
var clientHost = new SessionClientHost(cancellationToken, options);
var client = new ts.server.SessionClient(clientHost);

// This host is just a proxy for the clientHost, it uses the client
// host to answer server queries about files on disk
var serverHost = new SessionServerHost(clientHost);
var server = new ts.server.Session(serverHost, serverHost);

// Fake the connection between the client and the server
serverHost.writeMessage = client.onMessage.bind(client);
clientHost.writeMessage = server.onMessage.bind(server);

// Wire the client to the host to get notifications when a file is open
// or edited.
clientHost.setClient(client);

// Set the properties
this.client = client;
this.host = clientHost;
}
getHost() { return this.host; }
getLanguageService(): ts.LanguageService { return this.client; }
getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
}
}

Loading

0 comments on commit 6d7045e

Please sign in to comment.