From 8a8a412a7f24542a6b9ed1255ec7e67eadaa2c48 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 14 Jul 2015 16:51:22 -0700 Subject: [PATCH 01/13] Fix file endings. --- src/compiler/program.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 5ce4846b43b..ca12f6de03d 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -341,7 +341,7 @@ namespace ts { }); } - function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { if (!isDeclarationFile(sourceFile)) { let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); @@ -350,7 +350,7 @@ namespace ts { return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile); } }); - } + } function getOptionsDiagnostics(): Diagnostic[] { let allDiagnostics: Diagnostic[] = []; From b8b4c0f5d4a5b72f3418e502f97ef1ede6769ed9 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 14 Jul 2015 17:25:34 -0700 Subject: [PATCH 02/13] Bump version number. --- package.json | 2 +- src/compiler/program.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 82ab734d6f1..54162c53a57 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "http://typescriptlang.org/", - "version": "1.5.3", + "version": "1.6.0", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ diff --git a/src/compiler/program.ts b/src/compiler/program.ts index ca12f6de03d..d79f617e1cf 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -8,7 +8,7 @@ namespace ts { /* @internal */ export let ioWriteTime = 0; /** The version of the TypeScript compiler release */ - export const version = "1.5.3"; + export const version = "1.6.0"; export function findConfigFile(searchPath: string): string { let fileName = "tsconfig.json"; From 86b84054506ce6669175141de9655482ed510241 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 14 Jul 2015 18:09:42 -0700 Subject: [PATCH 03/13] Add configureNightly script. --- .gitignore | 1 + scripts/configureNightly.ts | 69 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 scripts/configureNightly.ts diff --git a/.gitignore b/.gitignore index 3147b8e8724..58a45545939 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ tests/baselines/reference/projectOutput/* tests/baselines/local/projectOutput/* tests/services/baselines/prototyping/local/* tests/services/browser/typescriptServices.js +scripts/configureNightly.js scripts/processDiagnosticMessages.d.ts scripts/processDiagnosticMessages.js scripts/importDefinitelyTypedTests.js diff --git a/scripts/configureNightly.ts b/scripts/configureNightly.ts new file mode 100644 index 00000000000..a0e30b007cf --- /dev/null +++ b/scripts/configureNightly.ts @@ -0,0 +1,69 @@ +/// + +/** + * A minimal description for a parsed package.json object. + */ +interface PackageJson { + name: string; + version: string; + keywords: string[]; +} + +function main(): void { + const sys = ts.sys; + if (sys.args.length < 2) { + sys.write("Usage:" + sys.newLine) + sys.write("\tnode configureNightly.js " + sys.newLine); + return; + } + + // Acquire the version from the package.json file and modify it appropriately. + const packageJsonFilePath = ts.normalizePath(sys.args[0]); + const packageJsonContents = sys.readFile(packageJsonFilePath); + const packageJsonValue: PackageJson = JSON.parse(packageJsonContents); + + const nightlyVersion = getNightlyVersionString(packageJsonValue.version); + + // Modify the package.json structure + packageJsonValue.version = nightlyVersion; + + if (packageJsonValue.name !== "typescript-nightly") { + packageJsonValue.name = "typescript-nightly"; + packageJsonValue.keywords.push("nightly", "alpha", "beta", "prerelease"); + } + + // Acquire and modify the source file that exposes the version string. + const tsFilePath = ts.normalizePath(sys.args[1]); + const tsFileContents = sys.readFile(tsFilePath); + const versionAssignmentRegExp = /export\s+const\s+version\s+=\s+".*";/; + const modifiedTsFileContents = tsFileContents.replace(versionAssignmentRegExp, `export const version = "${nightlyVersion}";`); + + // Ensure we are actually changing something - the user probably wants to know that the update failed. + if (tsFileContents === modifiedTsFileContents) { + throw `File '${tsFilePath}' did not contain pattern ${versionAssignmentRegExp}`; + } + + // Finally write the changes to disk. + sys.writeFile(packageJsonFilePath, JSON.stringify(packageJsonValue, /*replacer:*/ undefined, /*space:*/ 4)) + sys.writeFile(tsFilePath, modifiedTsFileContents); +} + +function getNightlyVersionString(versionString: string): string { + // If the version string already contains "-nightly", + // then get the base string and update based on that. + const dashNightlyPos = versionString.indexOf("-nightly"); + if (dashNightlyPos >= 0) { + versionString = versionString.slice(0, dashNightlyPos); + } + + // We're going to append a representation of the current time at the end of the current version. + // String.prototype.toISOString() returns a 24-character string formatted as 'YYYY-MM-DDTHH:mm:ss.sssZ', + // but we'd prefer to just use hyphens as separators instead of 'T', ':', and '.'. + // The trailing 'Z' in this string can be removed; UTC time will always be implicit here. + const now = new Date(); + const timeStr = now.toISOString().slice(0, -1).replace(/:|T|\./g, "-"); + + return `${versionString}-nightly-${timeStr}`; +} + +main(); \ No newline at end of file From 230ccd6262115605b295802861da175b9f254580 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 15 Jul 2015 12:56:00 -0700 Subject: [PATCH 04/13] Added a 'publish-nightly' task to to the Jakefile. --- Jakefile.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/Jakefile.js b/Jakefile.js index 33ad46c8b58..b1ec9f3024c 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -311,7 +311,7 @@ var processDiagnosticMessagesTs = path.join(scriptsDirectory, "processDiagnostic var diagnosticMessagesJson = path.join(compilerDirectory, "diagnosticMessages.json"); var diagnosticInfoMapTs = path.join(compilerDirectory, "diagnosticInformationMap.generated.ts"); -file(processDiagnosticMessagesTs) +file(processDiagnosticMessagesTs); // processDiagnosticMessages script compileFile(processDiagnosticMessagesJs, @@ -342,6 +342,65 @@ desc("Generates a diagnostic file in TypeScript based on an input JSON file"); task("generate-diagnostics", [diagnosticInfoMapTs]) +// Publish nightly +var configureNightlyJs = path.join(scriptsDirectory, "configureNightly.js"); +var configureNightlyTs = path.join(scriptsDirectory, "configureNightly.ts"); +var packageJson = "package.json"; +var programTs = path.join(compilerDirectory, "program.ts") + +file(configureNightlyTs); + +compileFile(/*outfile*/configureNightlyJs, + /*sources*/ [configureNightlyTs], + /*prereqs*/ [configureNightlyTs], + /*prefixes*/ [], + /*useBuiltCompiler*/ false, + /*noOutFile*/ false, + /*generateDeclarations*/ false, + /*outDir*/ undefined, + /*preserveConstEnums*/ undefined, + /*keepComments*/ false, + /*noResolve*/ false, + /*stripInternal*/ false, + /*callback*/ function () { + var cmd = "node " + configureNightlyJs + " " + packageJson + " " + programTs; + console.log(cmd); + var ex = jake.createExec([cmd]); + // Add listeners for output and error + ex.addListener("stdout", function(output) { + process.stdout.write(output); + }); + ex.addListener("stderr", function(error) { + process.stderr.write(error); + }); + ex.addListener("cmdEnd", function() { + complete(); + }); + ex.run(); + }); + +task("setDebugModeTrue", function() { + useDebugMode = true; +}); + +desc("Configure, build, test, and publish the nightly release."); +task("publish-nightly", [configureNightlyJs, "LKG", "clean", "setDebugModeTrue", "runtests"], function () { + var cmd = "npm publish"; + console.log(cmd); + var ex = jake.createExec([cmd]); + // Add listeners for output and error + ex.addListener("stdout", function(output) { + process.stdout.write(output); + }); + ex.addListener("stderr", function(error) { + process.stderr.write(error); + }); + ex.addListener("cmdEnd", function() { + complete(); + }); + ex.run(); +}, {async: true}); + // Local target to build the compiler and services var tscFile = path.join(builtLocalDirectory, compilerFilename); compileFile(tscFile, compilerSources, [builtLocalDirectory, copyright].concat(compilerSources), [copyright], /*useBuiltCompiler:*/ false); From 50198247bbd8093923aead123617d9c0c1eae4db Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 15 Jul 2015 14:02:47 -0700 Subject: [PATCH 05/13] Use 'exec'. --- Jakefile.js | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/Jakefile.js b/Jakefile.js index b1ec9f3024c..edfd14fc1af 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -365,18 +365,7 @@ compileFile(/*outfile*/configureNightlyJs, /*callback*/ function () { var cmd = "node " + configureNightlyJs + " " + packageJson + " " + programTs; console.log(cmd); - var ex = jake.createExec([cmd]); - // Add listeners for output and error - ex.addListener("stdout", function(output) { - process.stdout.write(output); - }); - ex.addListener("stderr", function(error) { - process.stderr.write(error); - }); - ex.addListener("cmdEnd", function() { - complete(); - }); - ex.run(); + exec(cmd, completeHandler, errorHandler) }); task("setDebugModeTrue", function() { @@ -387,18 +376,7 @@ desc("Configure, build, test, and publish the nightly release."); task("publish-nightly", [configureNightlyJs, "LKG", "clean", "setDebugModeTrue", "runtests"], function () { var cmd = "npm publish"; console.log(cmd); - var ex = jake.createExec([cmd]); - // Add listeners for output and error - ex.addListener("stdout", function(output) { - process.stdout.write(output); - }); - ex.addListener("stderr", function(error) { - process.stderr.write(error); - }); - ex.addListener("cmdEnd", function() { - complete(); - }); - ex.run(); + exec(cmd, completeHandler, errorHandler) }, {async: true}); // Local target to build the compiler and services From 8e93a49c7b6e2e8b964b45a4d0ed651e1aecc182 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 21 Jul 2015 16:05:03 -0700 Subject: [PATCH 06/13] Narrow exported session API, Unit tests for session API --- Jakefile.js | 1 + src/harness/harness.ts | 1 + src/server/session.ts | 90 +++--- tests/cases/unittests/session.ts | 458 +++++++++++++++++++++++++++++++ 4 files changed, 505 insertions(+), 45 deletions(-) create mode 100644 tests/cases/unittests/session.ts diff --git a/Jakefile.js b/Jakefile.js index 33ad46c8b58..9ff73d5af14 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -136,6 +136,7 @@ var harnessSources = [ "services/documentRegistry.ts", "services/preProcessFile.ts", "services/patternMatcher.ts", + "session.ts", "versionCache.ts", "convertToBase64.ts", "transpile.ts" diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 9f26887ff90..cf81e172527 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -29,6 +29,7 @@ var Buffer: BufferConstructor = require('buffer').Buffer; // this will work in the browser via browserify var _chai: typeof chai = require('chai'); var assert: typeof _chai.assert = _chai.assert; +var expect: typeof _chai.expect = _chai.expect; declare var __dirname: string; // Node-specific var global = Function("return this").call(null); diff --git a/src/server/session.ts b/src/server/session.ts index e0c540db18b..47c605f5b47 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -109,13 +109,13 @@ namespace ts.server { } export class Session { - projectService: ProjectService; - pendingOperation = false; - fileHash: ts.Map = {}; - nextFileId = 1; - errorTimer: any; /*NodeJS.Timer | number*/ - immediateId: any; - changeSeq = 0; + protected projectService: ProjectService; + private pendingOperation = false; + private fileHash: ts.Map = {}; + private nextFileId = 1; + private errorTimer: any; /*NodeJS.Timer | number*/ + private immediateId: any; + private changeSeq = 0; constructor( private host: ServerHost, @@ -129,7 +129,7 @@ namespace ts.server { }); } - handleEvent(eventName: string, project: Project, fileName: string) { + private handleEvent(eventName: string, project: Project, fileName: string) { if (eventName == "context") { this.projectService.log("got context event, updating diagnostics for" + fileName, "Info"); this.updateErrorCheck([{ fileName, project }], this.changeSeq, @@ -137,7 +137,7 @@ namespace ts.server { } } - logError(err: Error, cmd: string) { + public logError(err: Error, cmd: string) { var typedErr = err; var msg = "Exception on executing command " + cmd; if (typedErr.message) { @@ -149,11 +149,11 @@ namespace ts.server { this.projectService.log(msg); } - sendLineToClient(line: string) { + private sendLineToClient(line: string) { this.host.write(line + this.host.newLine); } - send(msg: protocol.Message) { + public send(msg: protocol.Message) { var json = JSON.stringify(msg); if (this.logger.isVerbose()) { this.logger.info(msg.type + ": " + json); @@ -162,7 +162,7 @@ namespace ts.server { '\r\n\r\n' + json); } - event(info: any, eventName: string) { + public event(info: any, eventName: string) { var ev: protocol.Event = { seq: 0, type: "event", @@ -172,7 +172,7 @@ namespace ts.server { this.send(ev); } - response(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { + private response(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { var res: protocol.Response = { seq: 0, type: "response", @@ -189,11 +189,11 @@ namespace ts.server { this.send(res); } - output(body: any, commandName: string, requestSequence = 0, errorMessage?: string) { + public output(body: any, commandName: string, requestSequence = 0, errorMessage?: string) { this.response(body, commandName, requestSequence, errorMessage); } - semanticCheck(file: string, project: Project) { + private semanticCheck(file: string, project: Project) { try { var diags = project.compilerService.languageService.getSemanticDiagnostics(file); @@ -207,7 +207,7 @@ namespace ts.server { } } - syntacticCheck(file: string, project: Project) { + private syntacticCheck(file: string, project: Project) { try { var diags = project.compilerService.languageService.getSyntacticDiagnostics(file); if (diags) { @@ -220,12 +220,12 @@ namespace ts.server { } } - errorCheck(file: string, project: Project) { + private errorCheck(file: string, project: Project) { this.syntacticCheck(file, project); this.semanticCheck(file, project); } - updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { + private updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { setTimeout(() => { if (matchSeq(seq)) { this.projectService.updateProjectStructure(); @@ -233,7 +233,7 @@ namespace ts.server { }, ms); } - updateErrorCheck(checkList: PendingErrorCheck[], seq: number, + private updateErrorCheck(checkList: PendingErrorCheck[], seq: number, matchSeq: (seq: number) => boolean, ms = 1500, followMs = 200) { if (followMs > ms) { followMs = ms; @@ -269,7 +269,7 @@ namespace ts.server { } } - getDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { + private getDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -291,7 +291,7 @@ namespace ts.server { })); } - getTypeDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { + private getTypeDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -313,7 +313,7 @@ namespace ts.server { })); } - getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[]{ + private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[]{ fileName = ts.normalizePath(fileName); let project = this.projectService.getProjectForFile(fileName); @@ -343,7 +343,7 @@ namespace ts.server { }); } - getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo { + private getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo { fileName = ts.normalizePath(fileName) let project = this.projectService.getProjectForFile(fileName) @@ -358,7 +358,7 @@ namespace ts.server { return projectInfo; } - getRenameLocations(line: number, offset: number, fileName: string,findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { + private getRenameLocations(line: number, offset: number, fileName: string,findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -426,7 +426,7 @@ namespace ts.server { return { info: renameInfo, locs: bakedRenameLocs }; } - getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody { + private getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody { // TODO: get all projects for this file; report refs for all projects deleting duplicates // can avoid duplicates by eliminating same ref file from subsequent projects var file = ts.normalizePath(fileName); @@ -473,12 +473,12 @@ namespace ts.server { }; } - openClientFile(fileName: string) { + private openClientFile(fileName: string) { var file = ts.normalizePath(fileName); this.projectService.openClientFile(file); } - getQuickInfo(line: number, offset: number, fileName: string): protocol.QuickInfoResponseBody { + private getQuickInfo(line: number, offset: number, fileName: string): protocol.QuickInfoResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -504,7 +504,7 @@ namespace ts.server { }; } - getFormattingEditsForRange(line: number, offset: number, endLine: number, endOffset: number, fileName: string): protocol.CodeEdit[] { + private getFormattingEditsForRange(line: number, offset: number, endLine: number, endOffset: number, fileName: string): protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -531,7 +531,7 @@ namespace ts.server { }); } - getFormattingEditsAfterKeystroke(line: number, offset: number, key: string, fileName: string): protocol.CodeEdit[] { + private getFormattingEditsAfterKeystroke(line: number, offset: number, key: string, fileName: string): protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -607,7 +607,7 @@ namespace ts.server { }); } - getCompletions(line: number, offset: number, prefix: string, fileName: string): protocol.CompletionEntry[] { + private getCompletions(line: number, offset: number, prefix: string, fileName: string): protocol.CompletionEntry[] { if (!prefix) { prefix = ""; } @@ -633,7 +633,7 @@ namespace ts.server { }, []).sort((a, b) => a.name.localeCompare(b.name)); } - getCompletionEntryDetails(line: number, offset: number, + private getCompletionEntryDetails(line: number, offset: number, entryNames: string[], fileName: string): protocol.CompletionEntryDetails[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -653,7 +653,7 @@ namespace ts.server { }, []); } - getSignatureHelpItems(line: number, offset: number, fileName: string): protocol.SignatureHelpItems { + private getSignatureHelpItems(line: number, offset: number, fileName: string): protocol.SignatureHelpItems { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -682,7 +682,7 @@ namespace ts.server { return result; } - getDiagnostics(delay: number, fileNames: string[]) { + private getDiagnostics(delay: number, fileNames: string[]) { var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(fileName); @@ -697,7 +697,7 @@ namespace ts.server { } } - change(line: number, offset: number, endLine: number, endOffset: number, insertString: string, fileName: string) { + private change(line: number, offset: number, endLine: number, endOffset: number, insertString: string, fileName: string) { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (project) { @@ -712,7 +712,7 @@ namespace ts.server { } } - reload(fileName: string, tempFileName: string, reqSeq = 0) { + private reload(fileName: string, tempFileName: string, reqSeq = 0) { var file = ts.normalizePath(fileName); var tmpfile = ts.normalizePath(tempFileName); var project = this.projectService.getProjectForFile(file); @@ -725,7 +725,7 @@ namespace ts.server { } } - saveToTmp(fileName: string, tempFileName: string) { + private saveToTmp(fileName: string, tempFileName: string) { var file = ts.normalizePath(fileName); var tmpfile = ts.normalizePath(tempFileName); @@ -735,12 +735,12 @@ namespace ts.server { } } - closeClientFile(fileName: string) { + private closeClientFile(fileName: string) { var file = ts.normalizePath(fileName); this.projectService.closeClientFile(file); } - decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] { + private decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] { if (!items) { return undefined; } @@ -759,7 +759,7 @@ namespace ts.server { })); } - getNavigationBarItems(fileName: string): protocol.NavigationBarItem[] { + private getNavigationBarItems(fileName: string): protocol.NavigationBarItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -775,7 +775,7 @@ namespace ts.server { return this.decorateNavigationBarItem(project, fileName, items); } - getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { + private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -814,7 +814,7 @@ namespace ts.server { }); } - getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] { + private getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -836,7 +836,7 @@ namespace ts.server { })); } - exit() { + public exit() { } private handlers : Map<(request: protocol.Request) => {response?: any, responseRequired?: boolean}> = { @@ -942,14 +942,14 @@ namespace ts.server { return {response: this.getProjectInfo(file, needFileNameList)}; }, }; - addProtocolHandler(command: string, handler: (request: protocol.Request) => {response?: any, responseRequired: boolean}) { + public addProtocolHandler(command: string, handler: (request: protocol.Request) => {response?: any, responseRequired: boolean}) { if (this.handlers[command]) { throw new Error(`Protocol handler already exists for command "${command}"`); } this.handlers[command] = handler; } - executeCommand(request: protocol.Request) : {response?: any, responseRequired?: boolean} { + public executeCommand(request: protocol.Request) : {response?: any, responseRequired?: boolean} { var handler = this.handlers[request.command]; if (handler) { return handler(request); @@ -960,7 +960,7 @@ namespace ts.server { } } - onMessage(message: string) { + public onMessage(message: string) { if (this.logger.isVerbose()) { this.logger.info("request: " + message); var start = this.hrtime(); diff --git a/tests/cases/unittests/session.ts b/tests/cases/unittests/session.ts new file mode 100644 index 00000000000..53353c12dda --- /dev/null +++ b/tests/cases/unittests/session.ts @@ -0,0 +1,458 @@ +/// + +module ts.server { + let lastWrittenToHost: string, + mockHost: ServerHost = { + args: [], + newLine: '\n', + useCaseSensitiveFileNames: true, + write: (s) => lastWrittenToHost = s, + readFile: () => void 0, + writeFile: () => void 0, + resolvePath: () => void 0, + fileExists: () => false, + directoryExists: () => false, + createDirectory: () => void 0, + getExecutingFilePath: () => void 0, + getCurrentDirectory: () => void 0, + readDirectory: () => void 0, + exit: () => void 0 + }, + mockLogger: Logger = { + close(): void {}, + isVerbose(): boolean { return false; }, + loggingEnabled(): boolean { return false; }, + perftrc(s: string): void {}, + info(s: string): void {}, + startGroup(): void {}, + endGroup(): void {}, + msg(s: string, type?: string): void {}, + }; + + describe('the Session class', () => { + let session:Session, + lastSent:protocol.Message; + + beforeEach(() => { + session = new Session(mockHost, Buffer.byteLength, process.hrtime, mockLogger); + session.send = (msg: protocol.Message) => { + lastSent = msg; + }; + }); + + describe('executeCommand', () => { + it('should throw when commands are executed with invalid arguments', () => { + let req : protocol.FileRequest = { + command: CommandNames.Open, + seq: 0, + type: 'command', + arguments: { + file: undefined + } + }; + + expect(() => session.executeCommand(req)).to.throw(); + }); + it('should output an error response when a command does not exist', () => { + let req : protocol.Request = { + command: 'foobar', + seq: 0, + type: 'command' + }; + + session.executeCommand(req); + + expect(lastSent).to.deep.equal({ + command: CommandNames.Unknown, + type: 'response', + seq: 0, + message: 'Unrecognized JSON command: foobar', + request_seq: 0, + success: false + }); + }); + it('should return a tuple containing the response and if a response is required on success', () => { + let req : protocol.ConfigureRequest = { + command: CommandNames.Configure, + seq: 0, + type: 'command', + arguments: { + hostInfo: 'unit test', + formatOptions: { + newLineCharacter: '`n' + } + } + }; + + expect(session.executeCommand(req)).to.deep.equal({ + responseRequired: false + }); + expect(lastSent).to.deep.equal({ + command: CommandNames.Configure, + type: 'response', + success: true, + request_seq: 0, + seq: 0, + body: undefined + }); + }); + }); + + describe('onMessage', () => { + it('should not throw when commands are executed with invalid arguments', () => { + let i = 0; + for (name in CommandNames) { + if (!Object.prototype.hasOwnProperty.call(CommandNames, name)) { + continue; + } + let req : protocol.Request = { + command: name, + seq: i++, + type: 'command' + }; + session.onMessage(JSON.stringify(req)); + req.seq+=2; + req.arguments = {}; + session.onMessage(JSON.stringify(req)); + req.seq+=2; + req.arguments = null; + session.onMessage(JSON.stringify(req)); + } + }); + it('should output the response for a correctly handled message', () => { + let req : protocol.ConfigureRequest = { + command: CommandNames.Configure, + seq: 0, + type: 'command', + arguments: { + hostInfo: 'unit test', + formatOptions: { + newLineCharacter: '`n' + } + } + }; + + session.onMessage(JSON.stringify(req)); + + expect(lastSent).to.deep.equal({ + command: CommandNames.Configure, + type: 'response', + success: true, + request_seq: 0, + seq: 0, + body: undefined + }); + }); + }); + + describe('exit', () => { + it('is a noop which can be handled by subclasses', () => { + session.exit(); //does nothing, should keep running tests + expect(session).to.exist; + }); + }); + + describe('send', () => { + it('is an overrideable handle which sends protocol messages over the wire', () => { + let msg = {seq: 0, type: 'none'}, + strmsg = JSON.stringify(msg), + len = 1+Buffer.byteLength(strmsg, 'utf8'), + resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`; + + session.send = Session.prototype.send; + assert(session.send); + expect(session.send(msg)).to.not.exist; + expect(lastWrittenToHost).to.equal(resultMsg); + }); + }); + + describe('addProtocolHandler', () => { + it('can add protocol handlers', () => { + let respBody = { + item: false + }, + command = 'newhandle', + result = { + response: respBody, + responseRequired: true + }; + + session.addProtocolHandler(command, (req) => result); + + expect(session.executeCommand({ + command, + seq: 0, + type: 'command' + })).to.deep.equal(result); + }); + it('throws when a duplicate handler is passed', () => { + let respBody = { + item: false + }, + resp = { + response: respBody, + responseRequired: true + }, + command = 'newhandle'; + + session.addProtocolHandler(command, (req) => resp); + + expect(() => session.addProtocolHandler(command, (req) => resp)) + .to.throw(`Protocol handler already exists for command "${command}"`); + }); + }); + + describe('event', () => { + it('can format event responses and send them', () => { + let evt = 'notify-test', + info = { + test: true + }; + + session.event(info, evt); + + expect(lastSent).to.deep.equal({ + type: 'event', + seq: 0, + event: evt, + body: info + }); + }); + }); + + describe('output', () => { + it('can format command responses and send them', () => { + let body = { + block: { + key: 'value' + } + }, + command = 'test'; + + session.output(body, command); + + expect(lastSent).to.deep.equal({ + seq: 0, + request_seq: 0, + type: 'response', + command, + body: body, + success: true + }); + }); + }); + }); + + describe('how Session is extendable via subclassing', () => { + let TestSession = class extends Session { + lastSent: protocol.Message; + customHandler:string = 'testhandler'; + constructor(){ + super(mockHost, Buffer.byteLength, process.hrtime, mockLogger); + this.addProtocolHandler(this.customHandler, () => { + return {response: undefined, responseRequired: true}; + }); + } + send(msg: protocol.Message) { + this.lastSent = msg; + } + }; + + it('can override methods such as send', () => { + let session = new TestSession(), + body = { + block: { + key: 'value' + } + }, + command = 'test'; + + session.output(body, command); + + expect(session.lastSent).to.deep.equal({ + seq: 0, + request_seq: 0, + type: 'response', + command, + body: body, + success: true + }); + }); + it('can add and respond to new protocol handlers', () => { + let session = new TestSession(); + + expect(session.executeCommand({ + seq: 0, + type: 'command', + command: session.customHandler + })).to.deep.equal({ + response: undefined, + responseRequired: true + }); + }); + it('has access to the project service', () => { + let ServiceSession = class extends TestSession { + constructor() { + super(); + assert(this.projectService); + expect(this.projectService).to.be.instanceOf(ProjectService); + } + }; + new ServiceSession(); + }); + }); + + describe('an example of using the Session API to create an in-process server', () => { + let inProcHost: ServerHost = { + args: [], + newLine: '\n', + useCaseSensitiveFileNames: true, + write: (s) => lastWrittenToHost = s, + readFile: () => void 0, + writeFile: () => void 0, + resolvePath: () => void 0, + fileExists: () => false, + directoryExists: () => false, + createDirectory: () => void 0, + getExecutingFilePath: () => void 0, + getCurrentDirectory: () => void 0, + readDirectory: () => void 0, + exit: () => void 0 + }, + InProcSession = class extends Session { + private queue: protocol.Request[] = []; + constructor(private client: {handle: (msg: protocol.Message) => void}) { + super(inProcHost, Buffer.byteLength, process.hrtime, mockLogger); + this.addProtocolHandler('echo', (req: protocol.Request) => ({ + response: req.arguments, + responseRequired: true + })); + } + + send(msg: protocol.Message) { + this.client.handle(msg); + } + + enqueue(msg: protocol.Request) { + this.queue = [msg].concat(this.queue); + } + + handleRequest(msg: protocol.Request) { + try { + var {response} = this.executeCommand(msg); + } catch (e) { + this.output(undefined, msg.command, msg.seq, e.toString()); + return; + } + if (response) { + this.output(response, msg.command, msg.seq); + } + } + + consumeQueue() { + while (this.queue.length > 0) { + let elem = this.queue[this.queue.length-1]; + this.queue = this.queue.slice(0,this.queue.length-1); + this.handleRequest(elem); + } + } + }, + InProcClient = class { + private server: Session&{enqueue: (msg: protocol.Request) => void}; + private seq: number = 0; + private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; + private eventHandlers: ts.Map<(args: any) => void> = {}; + + handle(msg: protocol.Message): void { + if (msg.type === 'response') { + var response = msg; + if (this.callbacks[response.request_seq]) { + this.callbacks[response.request_seq](response); + delete this.callbacks[response.request_seq]; + } + } else if (msg.type === 'event') { + var event = msg; + this.emit(event.event, event.body); + } + } + + emit(name: string, args: any): void { + if (!this.eventHandlers[name]) { + return; + } + this.eventHandlers[name](args); + } + + on(name: string, handler: (args: any) => void): void { + this.eventHandlers[name] = handler; + } + + connect(session: Session&{enqueue: (msg: protocol.Request) => void}): void { + this.server = session; + } + + execute(command: string, args: any, callback: (resp: protocol.Response) => void): void { + if (!this.server) { + return; + } + this.seq++; + this.server.enqueue({ + seq: this.seq, + type: 'command', + command, + arguments: args + }); + this.callbacks[this.seq] = callback; + } + }; + + it('can be constructed and respond to commands', (done) => { + let cli = new InProcClient(), + session = new InProcSession(cli), + toEcho = { + data: true + }, + toEvent = { + data: false + }, + responses = 0; + + //Connect the client + cli.connect(session); + + //add an event handler + cli.on('testevent', (eventinfo) => { + expect(eventinfo).to.equal(toEvent); + responses++; + expect(responses).to.equal(1); + }); + + //trigger said event from the server + session.event(toEvent,'testevent'); + + //Queue an echo command + cli.execute('echo', toEcho, (resp) => { + assert(resp.success, resp.message); + responses++; + expect(responses).to.equal(2); + expect(resp.body).to.deep.equal(toEcho); + }); + + //Queue a configure command + cli.execute('configure', { + hostInfo: 'unit test', + formatOptions: { + newLineCharacter: '`n' + } + }, (resp) => { + assert(resp.success, resp.message); + responses++; + expect(responses).to.equal(3); + done(); + }); + + //Consume the queue and trigger the callbacks + session.consumeQueue(); + }); + }); +} \ No newline at end of file From 8884f3b4f60881f00c510ab71ecf2776900151aa Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 21 Jul 2015 16:26:03 -0700 Subject: [PATCH 07/13] Fix tslint issues --- tests/cases/unittests/session.ts | 43 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/tests/cases/unittests/session.ts b/tests/cases/unittests/session.ts index 53353c12dda..1d7b9572bbb 100644 --- a/tests/cases/unittests/session.ts +++ b/tests/cases/unittests/session.ts @@ -30,8 +30,8 @@ module ts.server { }; describe('the Session class', () => { - let session:Session, - lastSent:protocol.Message; + let session: Session, + lastSent: protocol.Message; beforeEach(() => { session = new Session(mockHost, Buffer.byteLength, process.hrtime, mockLogger); @@ -111,10 +111,10 @@ module ts.server { type: 'command' }; session.onMessage(JSON.stringify(req)); - req.seq+=2; + req.seq += 2; req.arguments = {}; session.onMessage(JSON.stringify(req)); - req.seq+=2; + req.seq += 2; req.arguments = null; session.onMessage(JSON.stringify(req)); } @@ -147,7 +147,7 @@ module ts.server { describe('exit', () => { it('is a noop which can be handled by subclasses', () => { - session.exit(); //does nothing, should keep running tests + session.exit(); // Does nothing, should keep running tests expect(session).to.exist; }); }); @@ -156,7 +156,7 @@ module ts.server { it('is an overrideable handle which sends protocol messages over the wire', () => { let msg = {seq: 0, type: 'none'}, strmsg = JSON.stringify(msg), - len = 1+Buffer.byteLength(strmsg, 'utf8'), + len = 1 + Buffer.byteLength(strmsg, 'utf8'), resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`; session.send = Session.prototype.send; @@ -246,7 +246,7 @@ module ts.server { describe('how Session is extendable via subclassing', () => { let TestSession = class extends Session { lastSent: protocol.Message; - customHandler:string = 'testhandler'; + customHandler: string = 'testhandler'; constructor(){ super(mockHost, Buffer.byteLength, process.hrtime, mockLogger); this.addProtocolHandler(this.customHandler, () => { @@ -338,8 +338,9 @@ module ts.server { } handleRequest(msg: protocol.Request) { + let response: protocol.Response; try { - var {response} = this.executeCommand(msg); + response = this.executeCommand(msg).response; } catch (e) { this.output(undefined, msg.command, msg.seq, e.toString()); return; @@ -351,27 +352,27 @@ module ts.server { consumeQueue() { while (this.queue.length > 0) { - let elem = this.queue[this.queue.length-1]; - this.queue = this.queue.slice(0,this.queue.length-1); + let elem = this.queue[this.queue.length - 1]; + this.queue = this.queue.slice(0, this.queue.length - 1); this.handleRequest(elem); } } }, InProcClient = class { - private server: Session&{enqueue: (msg: protocol.Request) => void}; + private server: Session & {enqueue: (msg: protocol.Request) => void}; private seq: number = 0; private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; private eventHandlers: ts.Map<(args: any) => void> = {}; handle(msg: protocol.Message): void { if (msg.type === 'response') { - var response = msg; + let response = msg; if (this.callbacks[response.request_seq]) { this.callbacks[response.request_seq](response); delete this.callbacks[response.request_seq]; } } else if (msg.type === 'event') { - var event = msg; + let event = msg; this.emit(event.event, event.body); } } @@ -387,7 +388,7 @@ module ts.server { this.eventHandlers[name] = handler; } - connect(session: Session&{enqueue: (msg: protocol.Request) => void}): void { + connect(session: Session & {enqueue: (msg: protocol.Request) => void}): void { this.server = session; } @@ -417,20 +418,20 @@ module ts.server { }, responses = 0; - //Connect the client + // Connect the client cli.connect(session); - //add an event handler + // Add an event handler cli.on('testevent', (eventinfo) => { expect(eventinfo).to.equal(toEvent); responses++; expect(responses).to.equal(1); }); - //trigger said event from the server - session.event(toEvent,'testevent'); + // Trigger said event from the server + session.event(toEvent, 'testevent'); - //Queue an echo command + // Queue an echo command cli.execute('echo', toEcho, (resp) => { assert(resp.success, resp.message); responses++; @@ -438,7 +439,7 @@ module ts.server { expect(resp.body).to.deep.equal(toEcho); }); - //Queue a configure command + // Queue a configure command cli.execute('configure', { hostInfo: 'unit test', formatOptions: { @@ -451,7 +452,7 @@ module ts.server { done(); }); - //Consume the queue and trigger the callbacks + // Consume the queue and trigger the callbacks session.consumeQueue(); }); }); From d6998da76129f8574c4c3b947abd9ba9519cd58b Mon Sep 17 00:00:00 2001 From: Jason Freeman Date: Wed, 22 Jul 2015 15:27:25 -0700 Subject: [PATCH 08/13] Add getBaseTypes on TypeObject in services --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 3 +++ src/services/services.ts | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c09b069bee6..cc3341fb594 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -68,6 +68,7 @@ namespace ts { getPropertyOfType, getSignaturesOfType, getIndexTypeOfType, + getBaseTypes, getReturnTypeOfSignature, getSymbolsInScope, getSymbolAtLocation, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8d5596c6f64..21eed30c244 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1404,6 +1404,7 @@ namespace ts { getPropertyOfType(type: Type, propertyName: string): Symbol; getSignaturesOfType(type: Type, kind: SignatureKind): Signature[]; getIndexTypeOfType(type: Type, kind: IndexKind): Type; + getBaseTypes(type: InterfaceType): ObjectType[]; getReturnTypeOfSignature(signature: Signature): Type; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; @@ -1808,7 +1809,9 @@ namespace ts { typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic) outerTypeParameters: TypeParameter[]; // Outer type parameters (undefined if none) localTypeParameters: TypeParameter[]; // Local type parameters (undefined if none) + /* @internal */ resolvedBaseConstructorType?: Type; // Resolved base constructor type of class + /* @internal */ resolvedBaseTypes: ObjectType[]; // Resolved base types } diff --git a/src/services/services.ts b/src/services/services.ts index 16f5b0b91fe..47ccc5c73aa 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -48,6 +48,7 @@ namespace ts { getConstructSignatures(): Signature[]; getStringIndexType(): Type; getNumberIndexType(): Type; + getBaseTypes(): ObjectType[] } export interface Signature { @@ -682,6 +683,11 @@ namespace ts { getNumberIndexType(): Type { return this.checker.getIndexTypeOfType(this, IndexKind.Number); } + getBaseTypes(): ObjectType[] { + return this.flags & (TypeFlags.Class | TypeFlags.Interface) + ? this.checker.getBaseTypes(this) + : undefined; + } } class SignatureObject implements Signature { From 5fb5b244fbd9c1360b539798dd36ec9ab868badf Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 23 Jul 2015 11:14:19 -0700 Subject: [PATCH 09/13] Feedback from PR, lint fixes --- tests/cases/unittests/session.ts | 819 +++++++++++++++---------------- 1 file changed, 407 insertions(+), 412 deletions(-) diff --git a/tests/cases/unittests/session.ts b/tests/cases/unittests/session.ts index 1d7b9572bbb..1d2efbb498c 100644 --- a/tests/cases/unittests/session.ts +++ b/tests/cases/unittests/session.ts @@ -1,24 +1,24 @@ /// module ts.server { - let lastWrittenToHost: string, - mockHost: ServerHost = { - args: [], - newLine: '\n', - useCaseSensitiveFileNames: true, - write: (s) => lastWrittenToHost = s, - readFile: () => void 0, - writeFile: () => void 0, - resolvePath: () => void 0, - fileExists: () => false, - directoryExists: () => false, - createDirectory: () => void 0, - getExecutingFilePath: () => void 0, - getCurrentDirectory: () => void 0, - readDirectory: () => void 0, - exit: () => void 0 - }, - mockLogger: Logger = { + let lastWrittenToHost: string; + const mockHost: ServerHost = { + args: [], + newLine: "\n", + useCaseSensitiveFileNames: true, + write(s): void { lastWrittenToHost = s; }, + readFile(): string { return void 0; }, + writeFile(): void {}, + resolvePath(): string { return void 0; }, + fileExists: () => false, + directoryExists: () => false, + createDirectory(): void {}, + getExecutingFilePath(): string { return void 0; }, + getCurrentDirectory(): string { return void 0; }, + readDirectory(): string[] { return []; }, + exit(): void {} + }; + const mockLogger: Logger = { close(): void {}, isVerbose(): boolean { return false; }, loggingEnabled(): boolean { return false; }, @@ -27,433 +27,428 @@ module ts.server { startGroup(): void {}, endGroup(): void {}, msg(s: string, type?: string): void {}, - }; - - describe('the Session class', () => { - let session: Session, - lastSent: protocol.Message; + }; + + describe("the Session class", () => { + let session: Session; + let lastSent: protocol.Message; - beforeEach(() => { - session = new Session(mockHost, Buffer.byteLength, process.hrtime, mockLogger); - session.send = (msg: protocol.Message) => { - lastSent = msg; - }; - }); + beforeEach(() => { + session = new Session(mockHost, Buffer.byteLength, process.hrtime, mockLogger); + session.send = (msg: protocol.Message) => { + lastSent = msg; + }; + }); - describe('executeCommand', () => { - it('should throw when commands are executed with invalid arguments', () => { - let req : protocol.FileRequest = { - command: CommandNames.Open, - seq: 0, - type: 'command', - arguments: { - file: undefined - } - }; + describe("executeCommand", () => { + it("should throw when commands are executed with invalid arguments", () => { + const req: protocol.FileRequest = { + command: CommandNames.Open, + seq: 0, + type: "command", + arguments: { + file: undefined + } + }; - expect(() => session.executeCommand(req)).to.throw(); - }); - it('should output an error response when a command does not exist', () => { - let req : protocol.Request = { - command: 'foobar', - seq: 0, - type: 'command' - }; + expect(() => session.executeCommand(req)).to.throw(); + }); + it("should output an error response when a command does not exist", () => { + const req: protocol.Request = { + command: "foobar", + seq: 0, + type: "command" + }; - session.executeCommand(req); + session.executeCommand(req); - expect(lastSent).to.deep.equal({ - command: CommandNames.Unknown, - type: 'response', - seq: 0, - message: 'Unrecognized JSON command: foobar', - request_seq: 0, - success: false - }); - }); - it('should return a tuple containing the response and if a response is required on success', () => { - let req : protocol.ConfigureRequest = { - command: CommandNames.Configure, - seq: 0, - type: 'command', - arguments: { - hostInfo: 'unit test', - formatOptions: { - newLineCharacter: '`n' - } - } - }; + expect(lastSent).to.deep.equal({ + command: CommandNames.Unknown, + type: "response", + seq: 0, + message: "Unrecognized JSON command: foobar", + request_seq: 0, + success: false + }); + }); + it("should return a tuple containing the response and if a response is required on success", () => { + const req: protocol.ConfigureRequest = { + command: CommandNames.Configure, + seq: 0, + type: "command", + arguments: { + hostInfo: "unit test", + formatOptions: { + newLineCharacter: "`n" + } + } + }; - expect(session.executeCommand(req)).to.deep.equal({ - responseRequired: false - }); - expect(lastSent).to.deep.equal({ - command: CommandNames.Configure, - type: 'response', - success: true, - request_seq: 0, - seq: 0, - body: undefined - }); - }); - }); + expect(session.executeCommand(req)).to.deep.equal({ + responseRequired: false + }); + expect(lastSent).to.deep.equal({ + command: CommandNames.Configure, + type: "response", + success: true, + request_seq: 0, + seq: 0, + body: undefined + }); + }); + }); - describe('onMessage', () => { - it('should not throw when commands are executed with invalid arguments', () => { - let i = 0; - for (name in CommandNames) { - if (!Object.prototype.hasOwnProperty.call(CommandNames, name)) { - continue; - } - let req : protocol.Request = { - command: name, - seq: i++, - type: 'command' - }; - session.onMessage(JSON.stringify(req)); - req.seq += 2; - req.arguments = {}; - session.onMessage(JSON.stringify(req)); - req.seq += 2; - req.arguments = null; - session.onMessage(JSON.stringify(req)); - } - }); - it('should output the response for a correctly handled message', () => { - let req : protocol.ConfigureRequest = { - command: CommandNames.Configure, - seq: 0, - type: 'command', - arguments: { - hostInfo: 'unit test', - formatOptions: { - newLineCharacter: '`n' - } - } - }; + describe("onMessage", () => { + it("should not throw when commands are executed with invalid arguments", () => { + let i = 0; + for (name in CommandNames) { + if (!Object.prototype.hasOwnProperty.call(CommandNames, name)) { + continue; + } + const req: protocol.Request = { + command: name, + seq: i++, + type: "command" + }; + session.onMessage(JSON.stringify(req)); + req.seq = i++; + req.arguments = {}; + session.onMessage(JSON.stringify(req)); + req.seq = i++; + req.arguments = null; + session.onMessage(JSON.stringify(req)); + req.seq = i++; + req.arguments = ""; + session.onMessage(JSON.stringify(req)); + req.seq = i++; + req.arguments = 0; + session.onMessage(JSON.stringify(req)); + req.seq = i++; + req.arguments = []; + session.onMessage(JSON.stringify(req)); + } + session.onMessage("GARBAGE NON_JSON DATA"); + }); + it("should output the response for a correctly handled message", () => { + const req: protocol.ConfigureRequest = { + command: CommandNames.Configure, + seq: 0, + type: "command", + arguments: { + hostInfo: "unit test", + formatOptions: { + newLineCharacter: "`n" + } + } + }; - session.onMessage(JSON.stringify(req)); + session.onMessage(JSON.stringify(req)); - expect(lastSent).to.deep.equal({ - command: CommandNames.Configure, - type: 'response', - success: true, - request_seq: 0, - seq: 0, - body: undefined - }); - }); - }); + expect(lastSent).to.deep.equal({ + command: CommandNames.Configure, + type: "response", + success: true, + request_seq: 0, + seq: 0, + body: undefined + }); + }); + }); - describe('exit', () => { - it('is a noop which can be handled by subclasses', () => { - session.exit(); // Does nothing, should keep running tests - expect(session).to.exist; - }); - }); + describe("exit", () => { + it("is a noop which can be handled by subclasses", () => { + session.exit(); // Does nothing, should keep running tests + expect(session).to.exist; + }); + }); - describe('send', () => { - it('is an overrideable handle which sends protocol messages over the wire', () => { - let msg = {seq: 0, type: 'none'}, - strmsg = JSON.stringify(msg), - len = 1 + Buffer.byteLength(strmsg, 'utf8'), - resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`; + describe("send", () => { + it("is an overrideable handle which sends protocol messages over the wire", () => { + const msg = {seq: 0, type: "none"}; + const strmsg = JSON.stringify(msg); + const len = 1 + Buffer.byteLength(strmsg, "utf8"); + const resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`; - session.send = Session.prototype.send; - assert(session.send); - expect(session.send(msg)).to.not.exist; - expect(lastWrittenToHost).to.equal(resultMsg); - }); - }); + session.send = Session.prototype.send; + assert(session.send); + expect(session.send(msg)).to.not.exist; + expect(lastWrittenToHost).to.equal(resultMsg); + }); + }); - describe('addProtocolHandler', () => { - it('can add protocol handlers', () => { - let respBody = { - item: false - }, - command = 'newhandle', - result = { - response: respBody, - responseRequired: true - }; + describe("addProtocolHandler", () => { + it("can add protocol handlers", () => { + const respBody = { + item: false + }; + const command = "newhandle"; + const result = { + response: respBody, + responseRequired: true + }; - session.addProtocolHandler(command, (req) => result); + session.addProtocolHandler(command, (req) => result); - expect(session.executeCommand({ - command, - seq: 0, - type: 'command' - })).to.deep.equal(result); - }); - it('throws when a duplicate handler is passed', () => { - let respBody = { - item: false - }, - resp = { - response: respBody, - responseRequired: true - }, - command = 'newhandle'; + expect(session.executeCommand({ + command, + seq: 0, + type: "command" + })).to.deep.equal(result); + }); + it("throws when a duplicate handler is passed", () => { + const respBody = { + item: false + }; + const resp = { + response: respBody, + responseRequired: true + }; + const command = "newhandle"; - session.addProtocolHandler(command, (req) => resp); + session.addProtocolHandler(command, (req) => resp); - expect(() => session.addProtocolHandler(command, (req) => resp)) - .to.throw(`Protocol handler already exists for command "${command}"`); - }); - }); - - describe('event', () => { - it('can format event responses and send them', () => { - let evt = 'notify-test', - info = { - test: true - }; + expect(() => session.addProtocolHandler(command, (req) => resp)) + .to.throw(`Protocol handler already exists for command "${command}"`); + }); + }); + + describe("event", () => { + it("can format event responses and send them", () => { + const evt = "notify-test"; + const info = { + test: true + }; - session.event(info, evt); + session.event(info, evt); - expect(lastSent).to.deep.equal({ - type: 'event', - seq: 0, - event: evt, - body: info - }); - }); - }); + expect(lastSent).to.deep.equal({ + type: "event", + seq: 0, + event: evt, + body: info + }); + }); + }); - describe('output', () => { - it('can format command responses and send them', () => { - let body = { - block: { - key: 'value' - } - }, - command = 'test'; + describe("output", () => { + it("can format command responses and send them", () => { + const body = { + block: { + key: "value" + } + }; + const command = "test"; - session.output(body, command); + session.output(body, command); - expect(lastSent).to.deep.equal({ - seq: 0, - request_seq: 0, - type: 'response', - command, - body: body, - success: true - }); - }); - }); - }); + expect(lastSent).to.deep.equal({ + seq: 0, + request_seq: 0, + type: "response", + command, + body: body, + success: true + }); + }); + }); + }); - describe('how Session is extendable via subclassing', () => { - let TestSession = class extends Session { - lastSent: protocol.Message; - customHandler: string = 'testhandler'; - constructor(){ - super(mockHost, Buffer.byteLength, process.hrtime, mockLogger); - this.addProtocolHandler(this.customHandler, () => { - return {response: undefined, responseRequired: true}; - }); - } - send(msg: protocol.Message) { - this.lastSent = msg; - } - }; + describe("how Session is extendable via subclassing", () => { + class TestSession extends Session { + lastSent: protocol.Message; + customHandler: string = "testhandler"; + constructor(){ + super(mockHost, Buffer.byteLength, process.hrtime, mockLogger); + this.addProtocolHandler(this.customHandler, () => { + return {response: undefined, responseRequired: true}; + }); + } + send(msg: protocol.Message) { + this.lastSent = msg; + } + }; - it('can override methods such as send', () => { - let session = new TestSession(), - body = { - block: { - key: 'value' - } - }, - command = 'test'; + it("can override methods such as send", () => { + const session = new TestSession(); + const body = { + block: { + key: "value" + } + }; + const command = "test"; - session.output(body, command); + session.output(body, command); - expect(session.lastSent).to.deep.equal({ - seq: 0, - request_seq: 0, - type: 'response', - command, - body: body, - success: true - }); - }); - it('can add and respond to new protocol handlers', () => { - let session = new TestSession(); + expect(session.lastSent).to.deep.equal({ + seq: 0, + request_seq: 0, + type: "response", + command, + body: body, + success: true + }); + }); + it("can add and respond to new protocol handlers", () => { + const session = new TestSession(); - expect(session.executeCommand({ - seq: 0, - type: 'command', - command: session.customHandler - })).to.deep.equal({ - response: undefined, - responseRequired: true - }); - }); - it('has access to the project service', () => { - let ServiceSession = class extends TestSession { - constructor() { - super(); - assert(this.projectService); - expect(this.projectService).to.be.instanceOf(ProjectService); - } - }; - new ServiceSession(); - }); - }); + expect(session.executeCommand({ + seq: 0, + type: "command", + command: session.customHandler + })).to.deep.equal({ + response: undefined, + responseRequired: true + }); + }); + it("has access to the project service", () => { + class ServiceSession extends TestSession { + constructor() { + super(); + assert(this.projectService); + expect(this.projectService).to.be.instanceOf(ProjectService); + } + }; + new ServiceSession(); + }); + }); - describe('an example of using the Session API to create an in-process server', () => { - let inProcHost: ServerHost = { - args: [], - newLine: '\n', - useCaseSensitiveFileNames: true, - write: (s) => lastWrittenToHost = s, - readFile: () => void 0, - writeFile: () => void 0, - resolvePath: () => void 0, - fileExists: () => false, - directoryExists: () => false, - createDirectory: () => void 0, - getExecutingFilePath: () => void 0, - getCurrentDirectory: () => void 0, - readDirectory: () => void 0, - exit: () => void 0 - }, - InProcSession = class extends Session { - private queue: protocol.Request[] = []; - constructor(private client: {handle: (msg: protocol.Message) => void}) { - super(inProcHost, Buffer.byteLength, process.hrtime, mockLogger); - this.addProtocolHandler('echo', (req: protocol.Request) => ({ - response: req.arguments, - responseRequired: true - })); - } - - send(msg: protocol.Message) { - this.client.handle(msg); - } + describe("an example of using the Session API to create an in-process server", () => { + class InProcSession extends Session { + private queue: protocol.Request[] = []; + constructor(private client: {handle: (msg: protocol.Message) => void}) { + super(mockHost, Buffer.byteLength, process.hrtime, mockLogger); + this.addProtocolHandler("echo", (req: protocol.Request) => ({ + response: req.arguments, + responseRequired: true + })); + } + + send(msg: protocol.Message) { + this.client.handle(msg); + } - enqueue(msg: protocol.Request) { - this.queue = [msg].concat(this.queue); - } - - handleRequest(msg: protocol.Request) { - let response: protocol.Response; - try { - response = this.executeCommand(msg).response; - } catch (e) { - this.output(undefined, msg.command, msg.seq, e.toString()); - return; - } - if (response) { - this.output(response, msg.command, msg.seq); - } - } + enqueue(msg: protocol.Request) { + this.queue.unshift(msg); + } + + handleRequest(msg: protocol.Request) { + let response: protocol.Response; + try { + ({response} = this.executeCommand(msg)); + } + catch (e) { + this.output(undefined, msg.command, msg.seq, e.toString()); + return; + } + if (response) { + this.output(response, msg.command, msg.seq); + } + } - consumeQueue() { - while (this.queue.length > 0) { - let elem = this.queue[this.queue.length - 1]; - this.queue = this.queue.slice(0, this.queue.length - 1); - this.handleRequest(elem); - } - } - }, - InProcClient = class { - private server: Session & {enqueue: (msg: protocol.Request) => void}; - private seq: number = 0; - private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; - private eventHandlers: ts.Map<(args: any) => void> = {}; + consumeQueue() { + while (this.queue.length > 0) { + const elem = this.queue.pop(); + this.handleRequest(elem); + } + } + } + + class InProcClient { + private server: InProcSession; + private seq: number = 0; + private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; + private eventHandlers: ts.Map<(args: any) => void> = {}; - handle(msg: protocol.Message): void { - if (msg.type === 'response') { - let response = msg; - if (this.callbacks[response.request_seq]) { - this.callbacks[response.request_seq](response); - delete this.callbacks[response.request_seq]; - } - } else if (msg.type === 'event') { - let event = msg; - this.emit(event.event, event.body); - } - } + handle(msg: protocol.Message): void { + if (msg.type === "response") { + const response = msg; + if (this.callbacks[response.request_seq]) { + this.callbacks[response.request_seq](response); + delete this.callbacks[response.request_seq]; + } + } + else if (msg.type === "event") { + const event = msg; + this.emit(event.event, event.body); + } + } - emit(name: string, args: any): void { - if (!this.eventHandlers[name]) { - return; - } - this.eventHandlers[name](args); - } + emit(name: string, args: any): void { + if (this.eventHandlers[name]) { + this.eventHandlers[name](args); + } + } - on(name: string, handler: (args: any) => void): void { - this.eventHandlers[name] = handler; - } + on(name: string, handler: (args: any) => void): void { + this.eventHandlers[name] = handler; + } - connect(session: Session & {enqueue: (msg: protocol.Request) => void}): void { - this.server = session; - } - - execute(command: string, args: any, callback: (resp: protocol.Response) => void): void { - if (!this.server) { - return; - } - this.seq++; - this.server.enqueue({ - seq: this.seq, - type: 'command', - command, - arguments: args - }); - this.callbacks[this.seq] = callback; - } - }; - - it('can be constructed and respond to commands', (done) => { - let cli = new InProcClient(), - session = new InProcSession(cli), - toEcho = { - data: true - }, - toEvent = { - data: false - }, - responses = 0; + connect(session: InProcSession): void { + this.server = session; + } + + execute(command: string, args: any, callback: (resp: protocol.Response) => void): void { + if (!this.server) { + return; + } + this.seq++; + this.server.enqueue({ + seq: this.seq, + type: "command", + command, + arguments: args + }); + this.callbacks[this.seq] = callback; + } + }; + + it("can be constructed and respond to commands", (done) => { + const cli = new InProcClient(); + const session = new InProcSession(cli); + const toEcho = { + data: true + }; + const toEvent = { + data: false + }; + let responses = 0; - // Connect the client - cli.connect(session); - - // Add an event handler - cli.on('testevent', (eventinfo) => { - expect(eventinfo).to.equal(toEvent); - responses++; - expect(responses).to.equal(1); - }); - - // Trigger said event from the server - session.event(toEvent, 'testevent'); - - // Queue an echo command - cli.execute('echo', toEcho, (resp) => { - assert(resp.success, resp.message); - responses++; - expect(responses).to.equal(2); - expect(resp.body).to.deep.equal(toEcho); - }); - - // Queue a configure command - cli.execute('configure', { - hostInfo: 'unit test', - formatOptions: { - newLineCharacter: '`n' - } - }, (resp) => { - assert(resp.success, resp.message); - responses++; - expect(responses).to.equal(3); - done(); - }); - - // Consume the queue and trigger the callbacks - session.consumeQueue(); - }); - }); + // Connect the client + cli.connect(session); + + // Add an event handler + cli.on("testevent", (eventinfo) => { + expect(eventinfo).to.equal(toEvent); + responses++; + expect(responses).to.equal(1); + }); + + // Trigger said event from the server + session.event(toEvent, "testevent"); + + // Queue an echo command + cli.execute("echo", toEcho, (resp) => { + assert(resp.success, resp.message); + responses++; + expect(responses).to.equal(2); + expect(resp.body).to.deep.equal(toEcho); + }); + + // Queue a configure command + cli.execute("configure", { + hostInfo: "unit test", + formatOptions: { + newLineCharacter: "`n" + } + }, (resp) => { + assert(resp.success, resp.message); + responses++; + expect(responses).to.equal(3); + done(); + }); + + // Consume the queue and trigger the callbacks + session.consumeQueue(); + }); + }); } \ No newline at end of file From d1fe21dda965e917912c30fb9f2fbd1602b6cadc Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 23 Jul 2015 12:32:17 -0700 Subject: [PATCH 10/13] Publish to TypeScript itself, create a task to preview changes. --- Jakefile.js | 25 +++++++++++++------------ scripts/configureNightly.ts | 26 +++++++++++++++----------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/Jakefile.js b/Jakefile.js index edfd14fc1af..5f8fbfbfce6 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -361,23 +361,24 @@ compileFile(/*outfile*/configureNightlyJs, /*preserveConstEnums*/ undefined, /*keepComments*/ false, /*noResolve*/ false, - /*stripInternal*/ false, - /*callback*/ function () { - var cmd = "node " + configureNightlyJs + " " + packageJson + " " + programTs; - console.log(cmd); - exec(cmd, completeHandler, errorHandler) - }); + /*stripInternal*/ false); -task("setDebugModeTrue", function() { +task("setDebugMode", function() { useDebugMode = true; }); -desc("Configure, build, test, and publish the nightly release."); -task("publish-nightly", [configureNightlyJs, "LKG", "clean", "setDebugModeTrue", "runtests"], function () { - var cmd = "npm publish"; +task("configure-nightly", [configureNightlyJs], function() { + var cmd = "node " + configureNightlyJs + " " + packageJson + " " + programTs; console.log(cmd); - exec(cmd, completeHandler, errorHandler) -}, {async: true}); + exec(cmd); +}, { async: true }); + +desc("Configure, build, test, and publish the nightly release."); +task("publish-nightly", ["configure-nightly", "LKG", "clean", "setDebugMode", "runtests"], function () { + var cmd = "npm publish --tag next"; + console.log(cmd); + exec(cmd); +}); // Local target to build the compiler and services var tscFile = path.join(builtLocalDirectory, compilerFilename); diff --git a/scripts/configureNightly.ts b/scripts/configureNightly.ts index a0e30b007cf..640f330b376 100644 --- a/scripts/configureNightly.ts +++ b/scripts/configureNightly.ts @@ -27,11 +27,6 @@ function main(): void { // Modify the package.json structure packageJsonValue.version = nightlyVersion; - if (packageJsonValue.name !== "typescript-nightly") { - packageJsonValue.name = "typescript-nightly"; - packageJsonValue.keywords.push("nightly", "alpha", "beta", "prerelease"); - } - // Acquire and modify the source file that exposes the version string. const tsFilePath = ts.normalizePath(sys.args[1]); const tsFileContents = sys.readFile(tsFilePath); @@ -40,7 +35,16 @@ function main(): void { // Ensure we are actually changing something - the user probably wants to know that the update failed. if (tsFileContents === modifiedTsFileContents) { - throw `File '${tsFilePath}' did not contain pattern ${versionAssignmentRegExp}`; + let err = `\n '${tsFilePath}' was not updated while configuring for a nightly publish.\n `; + + if (tsFileContents.match(versionAssignmentRegExp)) { + err += `Ensure that you have not already run this script; otherwise, erase your changes using 'git checkout -- "${tsFilePath}"'.`; + } + else { + err += `The file seems to no longer have a string matching '${versionAssignmentRegExp}'.`; + } + + throw err + "\n"; } // Finally write the changes to disk. @@ -51,19 +55,19 @@ function main(): void { function getNightlyVersionString(versionString: string): string { // If the version string already contains "-nightly", // then get the base string and update based on that. - const dashNightlyPos = versionString.indexOf("-nightly"); + const dashNightlyPos = versionString.indexOf("-dev"); if (dashNightlyPos >= 0) { versionString = versionString.slice(0, dashNightlyPos); } // We're going to append a representation of the current time at the end of the current version. // String.prototype.toISOString() returns a 24-character string formatted as 'YYYY-MM-DDTHH:mm:ss.sssZ', - // but we'd prefer to just use hyphens as separators instead of 'T', ':', and '.'. - // The trailing 'Z' in this string can be removed; UTC time will always be implicit here. + // but we'd prefer to just remove separators and limit ourselves to YYYYMMDD. + // UTC time will always be implicit here. const now = new Date(); - const timeStr = now.toISOString().slice(0, -1).replace(/:|T|\./g, "-"); + const timeStr = now.toISOString().replace(/:|T|\.|-/g, "").slice(0, 8); - return `${versionString}-nightly-${timeStr}`; + return `${versionString}-dev.${timeStr}`; } main(); \ No newline at end of file From 90bbb7fb3c94300a406fc9deb6e3dcafdefed83b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 23 Jul 2015 12:33:34 -0700 Subject: [PATCH 11/13] More linting from PR --- tests/cases/unittests/session.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/cases/unittests/session.ts b/tests/cases/unittests/session.ts index 1d2efbb498c..25be08293f2 100644 --- a/tests/cases/unittests/session.ts +++ b/tests/cases/unittests/session.ts @@ -1,6 +1,6 @@ /// -module ts.server { +namespace ts.server { let lastWrittenToHost: string; const mockHost: ServerHost = { args: [], @@ -256,8 +256,8 @@ module ts.server { describe("how Session is extendable via subclassing", () => { class TestSession extends Session { lastSent: protocol.Message; - customHandler: string = "testhandler"; - constructor(){ + customHandler = "testhandler"; + constructor() { super(mockHost, Buffer.byteLength, process.hrtime, mockLogger); this.addProtocolHandler(this.customHandler, () => { return {response: undefined, responseRequired: true}; @@ -315,7 +315,7 @@ module ts.server { describe("an example of using the Session API to create an in-process server", () => { class InProcSession extends Session { private queue: protocol.Request[] = []; - constructor(private client: {handle: (msg: protocol.Message) => void}) { + constructor(private client: InProcClient) { super(mockHost, Buffer.byteLength, process.hrtime, mockLogger); this.addProtocolHandler("echo", (req: protocol.Request) => ({ response: req.arguments, @@ -355,7 +355,7 @@ module ts.server { class InProcClient { private server: InProcSession; - private seq: number = 0; + private seq = 0; private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; private eventHandlers: ts.Map<(args: any) => void> = {}; From ef0a289c771a09cf7c739df47cb6ca3d6489ed51 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 23 Jul 2015 12:50:24 -0700 Subject: [PATCH 12/13] Remove exit test --- tests/cases/unittests/session.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/cases/unittests/session.ts b/tests/cases/unittests/session.ts index 25be08293f2..c1dfd508942 100644 --- a/tests/cases/unittests/session.ts +++ b/tests/cases/unittests/session.ts @@ -155,13 +155,6 @@ namespace ts.server { }); }); - describe("exit", () => { - it("is a noop which can be handled by subclasses", () => { - session.exit(); // Does nothing, should keep running tests - expect(session).to.exist; - }); - }); - describe("send", () => { it("is an overrideable handle which sends protocol messages over the wire", () => { const msg = {seq: 0, type: "none"}; From b443cfecc9bb4f08439085afc031ce4a3d6aaca3 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 23 Jul 2015 12:53:41 -0700 Subject: [PATCH 13/13] Semicolons. --- Jakefile.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Jakefile.js b/Jakefile.js index 5f8fbfbfce6..5bfd4d7d278 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -336,17 +336,17 @@ file(diagnosticInfoMapTs, [processDiagnosticMessagesJs, diagnosticMessagesJson], complete(); }); ex.run(); -}, {async: true}) +}, {async: true}); desc("Generates a diagnostic file in TypeScript based on an input JSON file"); -task("generate-diagnostics", [diagnosticInfoMapTs]) +task("generate-diagnostics", [diagnosticInfoMapTs]); // Publish nightly var configureNightlyJs = path.join(scriptsDirectory, "configureNightly.js"); var configureNightlyTs = path.join(scriptsDirectory, "configureNightly.ts"); var packageJson = "package.json"; -var programTs = path.join(compilerDirectory, "program.ts") +var programTs = path.join(compilerDirectory, "program.ts"); file(configureNightlyTs); @@ -476,11 +476,11 @@ file(specMd, [word2mdJs, specWord], function () { child_process.exec(cmd, function () { complete(); }); -}, {async: true}) +}, {async: true}); desc("Generates a Markdown version of the Language Specification"); -task("generate-spec", [specMd]) +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 @@ -612,7 +612,7 @@ task("runtests", ["tests", builtLocalDirectory], function() { exec(cmd, deleteTemporaryProjectOutput); }, {async: true}); -desc("Generates code coverage data via instanbul") +desc("Generates code coverage data via instanbul"); task("generate-code-coverage", ["tests", builtLocalDirectory], function () { var cmd = 'istanbul cover node_modules/mocha/bin/_mocha -- -R min -t ' + testTimeout + ' ' + run; console.log(cmd); @@ -655,7 +655,7 @@ task("runtests-browser", ["tests", "browserify", builtLocalDirectory], function( function getDiffTool() { var program = process.env['DIFF'] if (!program) { - fail("Add the 'DIFF' environment variable to the path of the program you want to use.") + fail("Add the 'DIFF' environment variable to the path of the program you want to use."); } return program; } @@ -664,14 +664,14 @@ function getDiffTool() { desc("Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable"); task('diff', function () { var cmd = '"' + getDiffTool() + '" ' + refBaseline + ' ' + localBaseline; - console.log(cmd) + console.log(cmd); exec(cmd); }, {async: true}); desc("Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable"); task('diff-rwc', function () { var cmd = '"' + getDiffTool() + '" ' + refRwcBaseline + ' ' + localRwcBaseline; - console.log(cmd) + console.log(cmd); exec(cmd); }, {async: true});