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/Jakefile.js b/Jakefile.js
index bd5074b43d6..ac40e30f77a 100644
--- a/Jakefile.js
+++ b/Jakefile.js
@@ -138,6 +138,7 @@ var harnessSources = harnessCoreSources.concat([
"services/documentRegistry.ts",
"services/preProcessFile.ts",
"services/patternMatcher.ts",
+ "session.ts",
"versionCache.ts",
"convertToBase64.ts",
"transpile.ts"
@@ -313,7 +314,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,
@@ -338,12 +339,50 @@ 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");
+
+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);
+
+task("setDebugMode", function() {
+ useDebugMode = true;
+});
+
+task("configure-nightly", [configureNightlyJs], function() {
+ var cmd = "node " + configureNightlyJs + " " + packageJson + " " + programTs;
+ console.log(cmd);
+ 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);
compileFile(tscFile, compilerSources, [builtLocalDirectory, copyright].concat(compilerSources), [copyright], /*useBuiltCompiler:*/ false);
@@ -440,11 +479,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
@@ -576,7 +615,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);
@@ -619,7 +658,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;
}
@@ -628,14 +667,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});
diff --git a/package.json b/package.json
index 70ada6fef15..c8b30f9de1c 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/scripts/configureNightly.ts b/scripts/configureNightly.ts
new file mode 100644
index 00000000000..640f330b376
--- /dev/null
+++ b/scripts/configureNightly.ts
@@ -0,0 +1,73 @@
+///
+
+/**
+ * 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;
+
+ // 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) {
+ 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.
+ 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("-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 remove separators and limit ourselves to YYYYMMDD.
+ // UTC time will always be implicit here.
+ const now = new Date();
+ const timeStr = now.toISOString().replace(/:|T|\.|-/g, "").slice(0, 8);
+
+ return `${versionString}-dev.${timeStr}`;
+}
+
+main();
\ No newline at end of file
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index b32ce5f6af0..3363987322a 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/program.ts b/src/compiler/program.ts
index 5ce4846b43b..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";
@@ -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[] = [];
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 0a3fd4bcb51..4bc244a48c2 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/harness/harness.ts b/src/harness/harness.ts
index 17aa0dd325c..b6c10071cb1 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 d2ac429aa3c..9a5cee32264 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), responseRequired: true};
},
};
- 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/src/services/services.ts b/src/services/services.ts
index 2a0516b2d34..7769e9b2fc0 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 {
diff --git a/tests/cases/unittests/session.ts b/tests/cases/unittests/session.ts
new file mode 100644
index 00000000000..c1dfd508942
--- /dev/null
+++ b/tests/cases/unittests/session.ts
@@ -0,0 +1,447 @@
+///
+
+namespace ts.server {
+ 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; },
+ 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;
+ let 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", () => {
+ 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", () => {
+ const 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", () => {
+ 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
+ });
+ });
+ });
+
+ 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));
+
+ expect(lastSent).to.deep.equal({
+ command: CommandNames.Configure,
+ type: "response",
+ success: true,
+ request_seq: 0,
+ seq: 0,
+ body: undefined
+ });
+ });
+ });
+
+ 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);
+ });
+ });
+
+ 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);
+
+ 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);
+
+ 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);
+
+ expect(lastSent).to.deep.equal({
+ type: "event",
+ seq: 0,
+ event: evt,
+ body: info
+ });
+ });
+ });
+
+ describe("output", () => {
+ it("can format command responses and send them", () => {
+ const body = {
+ block: {
+ key: "value"
+ }
+ };
+ const 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", () => {
+ class TestSession extends Session {
+ lastSent: protocol.Message;
+ customHandler = "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", () => {
+ const session = new TestSession();
+ const body = {
+ block: {
+ key: "value"
+ }
+ };
+ const 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", () => {
+ 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", () => {
+ 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", () => {
+ class InProcSession extends Session {
+ private queue: protocol.Request[] = [];
+ constructor(private client: InProcClient) {
+ 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.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) {
+ const elem = this.queue.pop();
+ this.handleRequest(elem);
+ }
+ }
+ }
+
+ class InProcClient {
+ private server: InProcSession;
+ private seq = 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") {
+ 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]) {
+ this.eventHandlers[name](args);
+ }
+ }
+
+ on(name: string, handler: (args: any) => void): void {
+ this.eventHandlers[name] = handler;
+ }
+
+ 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();
+ });
+ });
+}
\ No newline at end of file