diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index b4cdd3d9944..3a8a513d135 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1600,16 +1600,16 @@ module ts { var emitLeadingCommentsOfPosition = compilerOptions.removeComments ? (pos: number) => { } : emitLeadingCommentsOfLocalPosition; var detachedCommentsInfo: { nodePos: number; detachedCommentEndPos: number }[]; + /** Emit detached comments of the node */ var emitDetachedComments = compilerOptions.removeComments ? (node: TextRange) => { } : emitDetachedCommentsAtPosition; - /** Emits /// or pinned which is comment starting with /*! comments */ - var emitPinnedOrTripleSlashComments = compilerOptions.removeComments ? (node: Node) => { } : emitPinnedOrTripleSlashCommentsOfNode; - var writeComment = writeCommentRange; /** Emit a node */ - var emit = emitNode; + var emitNodeWithoutSourceMap = compilerOptions.removeComments ? emitNodeWithoutSourceMapWithoutComments : emitNodeWithoutSourceMapWithComments; + var emit = emitNodeWithoutSourceMap; + var emitWithoutComments = emitNodeWithoutSourceMapWithoutComments; /** Called just before starting emit of a node */ var emitStart = function (node: Node) { }; @@ -2071,25 +2071,34 @@ module ts { sourceMapDir = getDirectoryPath(normalizePath(jsFilePath)); } - function emitNodeWithMap(node: Node) { + function emitNodeWithSourceMap(node: Node) { if (node) { if (nodeIsSynthesized(node)) { return emitNode(node); } if (node.kind != SyntaxKind.SourceFile) { recordEmitNodeStartSpan(node); - emitNode(node); + emitNodeWithoutSourceMap(node); recordEmitNodeEndSpan(node); } else { recordNewSourceFileStart(node); - emitNode(node); + emitNodeWithoutSourceMap(node); } } } + function emitNodeWithSourceMapWithoutComments(node: Node) { + if (node) { + recordEmitNodeStartSpan(node); + emitNodeWithoutSourceMapWithoutComments(node); + recordEmitNodeEndSpan(node); + } + } + writeEmittedFiles = writeJavaScriptAndSourceMapFile; - emit = emitNodeWithMap; + emit = emitNodeWithSourceMap; + emitWithoutComments = emitNodeWithSourceMapWithoutComments; emitStart = recordEmitNodeStartSpan; emitEnd = recordEmitNodeEndSpan; emitToken = writeTextWithSpanRecord; @@ -3058,8 +3067,10 @@ module ts { return false; } - function indentIfOnDifferentLines(parent: Node, node1: Node, node2: Node) { - // Use a newline for existing code if the original had one, and we're preserving formatting. + // Returns 'true' if the code was actually indented, false otherwise. + // If the code is not indented, an optional valueToWriteWhenNotIndenting will be + // emitted instead. + function indentIfOnDifferentLines(parent: Node, node1: Node, node2: Node, valueToWriteWhenNotIndenting?: string): boolean { var realNodesAreOnDifferentLines = preserveNewLines && !nodeIsSynthesized(parent) && !nodeEndIsOnSameLineAsNodeStart(node1, node2); // Always use a newline for synthesized code if the synthesizer desires it. @@ -3070,8 +3081,12 @@ module ts { writeLine(); return true; } - - return false; + else { + if (valueToWriteWhenNotIndenting) { + write(valueToWriteWhenNotIndenting); + } + return false; + } } function emitPropertyAccess(node: PropertyAccessExpression) { @@ -3080,18 +3095,11 @@ module ts { } emit(node.expression); - - var indented = indentIfOnDifferentLines(node, node.expression, node.dotToken); - + var indentedBeforeDot = indentIfOnDifferentLines(node, node.expression, node.dotToken); write("."); - - indented = indented || indentIfOnDifferentLines(node, node.dotToken, node.name); - + var indentedAfterDot = indentIfOnDifferentLines(node, node.dotToken, node.name); emit(node.name); - - if (indented) { - decreaseIndent(); - } + decreaseIndentIf(indentedBeforeDot, indentedAfterDot); } function emitQualifiedName(node: QualifiedName) { @@ -3324,33 +3332,11 @@ module ts { } else { emit(node.left); - - // If there was a newline between the left side of the binary expression and the - // operator, then try to preserve that. - var indented1 = indentIfOnDifferentLines(node, node.left, node.operatorToken); - - // Otherwise just emit the operator right afterwards. For everything but - // comma, emit a space before the operator. - if (!indented1 && node.operatorToken.kind !== SyntaxKind.CommaToken) { - write(" "); - } - + var indentedBeforeOperator = indentIfOnDifferentLines(node, node.left, node.operatorToken, node.operatorToken.kind !== SyntaxKind.CommaToken ? " " : undefined); write(tokenToString(node.operatorToken.kind)); - - if (!indented1) { - var indented2 = indentIfOnDifferentLines(node, node.operatorToken, node.right); - } - - if (!indented2) { - write(" "); - } - + var indentedAfterOperator = indentIfOnDifferentLines(node, node.operatorToken, node.right, " "); emit(node.right); - - // If we indented the left or the right side, then dedent now. - if (indented1 || indented2) { - decreaseIndent(); - } + decreaseIndentIf(indentedBeforeOperator, indentedAfterOperator); } } @@ -3360,43 +3346,27 @@ module ts { function emitConditionalExpression(node: ConditionalExpression) { emit(node.condition); - var indent1 = indentIfOnDifferentLines(node, node.condition, node.questionToken); - if (!indent1) { - write(" "); - } - + var indentedBeforeQuestion = indentIfOnDifferentLines(node, node.condition, node.questionToken, " "); write("?"); - - if (!indent1) { - var indent2 = indentIfOnDifferentLines(node, node.questionToken, node.whenTrue); - } - - if (!indent2) { - write(" "); - } - + var indentedAfterQuestion = indentIfOnDifferentLines(node, node.questionToken, node.whenTrue, " "); emit(node.whenTrue); + decreaseIndentIf(indentedBeforeQuestion, indentedAfterQuestion); + var indentedBeforeColon = indentIfOnDifferentLines(node, node.whenTrue, node.colonToken, " "); + write(":"); + var indentedAfterColon = indentIfOnDifferentLines(node, node.colonToken, node.whenFalse, " "); + emit(node.whenFalse); + decreaseIndentIf(indentedBeforeColon, indentedAfterColon); + } - if (indent1 || indent2) { + // Helper function to decrease the indent if we previously indented. Allows multiple + // previous indent values to be considered at a time. This also allows caller to just + // call this once, passing in all their appropriate indent values, instead of needing + // to call this helper function multiple times. + function decreaseIndentIf(value1: boolean, value2?: boolean) { + if (value1) { decreaseIndent(); } - - var indent3 = indentIfOnDifferentLines(node, node.whenTrue, node.colonToken); - if (!indent3) { - write(" "); - } - - write(":"); - if (!indent3) { - var indent4 = indentIfOnDifferentLines(node, node.colonToken, node.whenFalse); - } - - if (!indent4) { - write(" "); - } - - emit(node.whenFalse); - if (indent3 || indent4) { + if (value2) { decreaseIndent(); } } @@ -3839,7 +3809,7 @@ module ts { emitContainingModuleName(node); write("."); } - emitNode(node.name); + emitNodeWithoutSourceMap(node.name); emitEnd(node.name); } @@ -3858,10 +3828,10 @@ module ts { emitStart(specifier.name); emitContainingModuleName(specifier); write("."); - emitNode(specifier.name); + emitNodeWithoutSourceMap(specifier.name); emitEnd(specifier.name); write(" = "); - emitNode(name); + emitNodeWithoutSourceMap(name); write(";"); }); } @@ -4267,14 +4237,14 @@ module ts { writeLine(); emitStart(p); write("if ("); - emitNode(p.name); + emitNodeWithoutSourceMap(p.name); write(" === void 0)"); emitEnd(p); write(" { "); emitStart(p); - emitNode(p.name); + emitNodeWithoutSourceMap(p.name); write(" = "); - emitNode(p.initializer); + emitNodeWithoutSourceMap(p.initializer); emitEnd(p); write("; }"); } @@ -4291,7 +4261,7 @@ module ts { emitLeadingComments(restParam); emitStart(restParam); write("var "); - emitNode(restParam.name); + emitNodeWithoutSourceMap(restParam.name); write(" = [];"); emitEnd(restParam); emitTrailingComments(restParam); @@ -4312,7 +4282,7 @@ module ts { increaseIndent(); writeLine(); emitStart(restParam); - emitNode(restParam.name); + emitNodeWithoutSourceMap(restParam.name); write("[" + tempName + " - " + restIndex + "] = arguments[" + tempName + "];"); emitEnd(restParam); decreaseIndent(); @@ -4333,7 +4303,7 @@ module ts { function emitDeclarationName(node: Declaration) { if (node.name) { - emitNode(node.name); + emitNodeWithoutSourceMap(node.name); } else { write(resolver.getGeneratedNameForNode(node)); @@ -4494,7 +4464,7 @@ module ts { // Don't emit comments on this body. We'll have already taken care of it above // when we called emitDetachedComments. - emitNode(body, /*disableComments:*/ true); + emitWithoutComments(body); emitEnd(body); write(";"); emitTempDeclarations(/*newLine*/ false); @@ -4505,7 +4475,10 @@ module ts { writeLine(); emitLeadingComments(node.body); write("return "); - emit(node.body, /*disableComments:*/ true); + + // Don't emit comments on this body. We'll have already taken care of it above + // when we called emitDetachedComments. + emitWithoutComments(node.body); write(";"); emitTrailingComments(node.body); @@ -4583,7 +4556,7 @@ module ts { emitStart(param); emitStart(param.name); write("this."); - emitNode(param.name); + emitNodeWithoutSourceMap(param.name); emitEnd(param.name); write(" = "); emit(param.name); @@ -4597,7 +4570,7 @@ module ts { // TODO: (jfreeman,drosen): comment on why this is emitNode instead of emit here. if (memberName.kind === SyntaxKind.StringLiteral || memberName.kind === SyntaxKind.NumericLiteral) { write("["); - emitNode(memberName); + emitNodeWithoutSourceMap(memberName); write("]"); } else if (memberName.kind === SyntaxKind.ComputedPropertyName) { @@ -4605,7 +4578,7 @@ module ts { } else { write("."); - emitNode(memberName); + emitNodeWithoutSourceMap(memberName); } } @@ -5098,11 +5071,11 @@ module ts { emitStart(specifier); emitContainingModuleName(specifier); write("."); - emitNode(specifier.name); + emitNodeWithoutSourceMap(specifier.name); write(" = "); write(generatedName); write("."); - emitNode(specifier.propertyName || specifier.name); + emitNodeWithoutSourceMap(specifier.propertyName || specifier.name); write(";"); emitEnd(specifier); }); @@ -5366,7 +5339,7 @@ module ts { emitLeadingComments(node.endOfFileToken); } - function emitNode(node: Node, disableComments?:boolean): void { + function emitNodeWithoutSourceMapWithComments(node: Node): void { if (!node) { return; } @@ -5375,7 +5348,7 @@ module ts { return emitPinnedOrTripleSlashComments(node); } - var emitComments = !disableComments && shouldEmitLeadingAndTrailingComments(node); + var emitComments = shouldEmitLeadingAndTrailingComments(node); if (emitComments) { emitLeadingComments(node); } @@ -5387,6 +5360,18 @@ module ts { } } + function emitNodeWithoutSourceMapWithoutComments(node: Node): void { + if (!node) { + return; + } + + if (node.flags & NodeFlags.Ambient) { + return emitPinnedOrTripleSlashComments(node); + } + + emitJavaScriptWorker(node); + } + function shouldEmitLeadingAndTrailingComments(node: Node) { switch (node.kind) { // All of these entities are emitted in a specialized fashion. As such, we allow @@ -5681,7 +5666,8 @@ module ts { } } - function emitPinnedOrTripleSlashCommentsOfNode(node: Node) { + /** Emits /// or pinned which is comment starting with /*! comments */ + function emitPinnedOrTripleSlashComments(node: Node) { var pinnedComments = ts.filter(getLeadingCommentsToEmit(node), isPinnedOrTripleSlashComment); function isPinnedOrTripleSlashComment(comment: CommentRange) { diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index e4979f7a3c4..d4dd8e31258 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -152,7 +152,6 @@ class CompilerBaselineRunner extends RunnerBase { if (this.errors) { Harness.Baseline.runBaseline('Correct errors for ' + fileName, justName.replace(/\.ts$/, '.errors.txt'), (): string => { if (result.errors.length === 0) return null; - return getErrorBaseline(toBeCompiled, otherFiles, result); }); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 8cbc48d9dce..90ffcef07ff 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -835,7 +835,7 @@ module Harness { // Register input files function register(file: { unitName: string; content: string; }) { if (file.content !== undefined) { - var fileName = ts.normalizeSlashes(file.unitName); + var fileName = ts.normalizePath(file.unitName); filemap[getCanonicalFileName(fileName)] = createSourceFileAndAssertInvariants(fileName, file.content, scriptTarget); } }; @@ -844,6 +844,7 @@ module Harness { return { getCurrentDirectory, getSourceFile: (fn, languageVersion) => { + fn = ts.normalizePath(fn); if (Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(fn))) { return filemap[getCanonicalFileName(fn)]; } @@ -1078,16 +1079,6 @@ module Harness { } }); - var filemap: { [name: string]: ts.SourceFile; } = {}; - var register = (file: { unitName: string; content: string; }) => { - if (file.content !== undefined) { - var fileName = ts.normalizeSlashes(file.unitName); - filemap[getCanonicalFileName(fileName)] = createSourceFileAndAssertInvariants(fileName, file.content, options.target, assertInvariants); - } - }; - inputFiles.forEach(register); - otherFiles.forEach(register); - var fileOutputs: GeneratedFile[] = []; var programFiles = inputFiles.map(file => file.unitName); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 4206f16bab3..68ac8d916fb 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -469,6 +469,7 @@ module Harness.LanguageService { this.writeMessage(message); } + readFile(fileName: string): string { if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) { fileName = Harness.Compiler.defaultLibFileName; @@ -526,6 +527,15 @@ module Harness.LanguageService { msg(message: string) { return this.host.log(message); } + + loggingEnabled() { + return true; + } + + isVerbose() { + return false; + } + endGroup(): void { } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5ca4cb4d00f..7c9ccfa7854 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -5,6 +5,8 @@ module ts.server { export interface Logger { close(): void; + isVerbose(): boolean; + loggingEnabled(): boolean; perftrc(s: string): void; info(s: string): void; startGroup(): void; @@ -1071,6 +1073,7 @@ module ts.server { static changeNumberThreshold = 8; static changeLengthThreshold = 256; + static maxVersions = 8; // REVIEW: can optimize by coalescing simple edits edit(pos: number, deleteLen: number, insertedText?: string) { @@ -1131,6 +1134,13 @@ module ts.server { this.currentVersion = snap.version; this.versions[snap.version] = snap; this.changes = []; + if ((this.currentVersion - this.minVersion) >= ScriptVersionCache.maxVersions) { + var oldMin = this.minVersion; + this.minVersion = (this.currentVersion - ScriptVersionCache.maxVersions) + 1; + for (var j = oldMin; j < this.minVersion; j++) { + this.versions[j] = undefined; + } + } } return snap; } diff --git a/src/server/server.ts b/src/server/server.ts index c48ba951347..4c13be80c4c 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -19,7 +19,7 @@ module ts.server { inGroup = false; firstInGroup = true; - constructor(public logFilename: string) { + constructor(public logFilename: string, public level: string) { } static padStringRight(str: string, padding: string) { @@ -51,9 +51,20 @@ module ts.server { this.firstInGroup = true; } + loggingEnabled() { + return !!this.logFilename; + } + + isVerbose() { + return this.loggingEnabled() && (this.level == "verbose"); + } + + msg(s: string, type = "Err") { if (this.fd < 0) { - this.fd = fs.openSync(this.logFilename, "w"); + if (this.logFilename) { + this.fd = fs.openSync(this.logFilename, "w"); + } } if (this.fd >= 0) { s = s + "\n"; @@ -173,17 +184,61 @@ module ts.server { }); rl.on('close',() => { - this.projectService.closeLog(); this.projectService.log("Exiting..."); + this.projectService.closeLog(); process.exit(0); }); } } + interface LogOptions { + file?: string; + detailLevel?: string; + } + + function parseLoggingEnvironmentString(logEnvStr: string): LogOptions { + var logEnv: LogOptions = {}; + var args = logEnvStr.split(' '); + for (var i = 0, len = args.length; i < (len - 1); i += 2) { + var option = args[i]; + var value = args[i + 1]; + if (option && value) { + switch (option) { + case "-file": + logEnv.file = value; + break; + case "-level": + logEnv.detailLevel = value; + break; + } + } + } + return logEnv; + } + + // TSS_LOG "{ level: "normal | verbose | terse", file?: string}" + function createLoggerFromEnv() { + var fileName: string = undefined; + var detailLevel = "normal"; + var logEnvStr = process.env["TSS_LOG"]; + if (logEnvStr) { + var logEnv = parseLoggingEnvironmentString(logEnvStr); + if (logEnv.file) { + fileName = logEnv.file; + } + else { + fileName = __dirname + "/.log" + process.pid.toString(); + } + if (logEnv.detailLevel) { + detailLevel = logEnv.detailLevel; + } + } + return new Logger(fileName, detailLevel); + } // This places log file in the directory containing editorServices.js // TODO: check that this location is writable - var logger = new Logger(__dirname + "/.log" + process.pid.toString()); + var logger = createLoggerFromEnv(); // REVIEW: for now this implementation uses polling. // The advantage of polling is that it works reliably diff --git a/src/server/session.ts b/src/server/session.ts index 7e9c6972a1f..cc705d64d5e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -145,6 +145,9 @@ module ts.server { send(msg: NodeJS._debugger.Message) { var json = JSON.stringify(msg); + if (this.logger.isVerbose()) { + this.logger.info(msg.type + ": " + json); + } this.sendLineToClient('Content-Length: ' + (1 + Buffer.byteLength(json, 'utf8')) + '\r\n\r\n' + json); } @@ -461,19 +464,49 @@ module ts.server { var compilerService = project.compilerService; var position = compilerService.host.lineColToPosition(file, line, col); var edits = compilerService.languageService.getFormattingEditsAfterKeystroke(file, position, key, - compilerService.formatCodeOptions); + compilerService.formatCodeOptions); + // Check whether we should auto-indent. This will be when + // the position is on a line containing only whitespace. + // This should leave the edits returned from + // getFormattingEditsAfterKeytroke either empty or pertaining + // only to the previous line. If all this is true, then + // add edits necessary to properly indent the current line. if ((key == "\n") && ((!edits) || (edits.length == 0) || allEditsBeforePos(edits, position))) { - // TODO: get these options from host - var editorOptions: ts.EditorOptions = { - IndentSize: 4, - TabSize: 4, - NewLineCharacter: "\n", - ConvertTabsToSpaces: true, - }; - var indentPosition = compilerService.languageService.getIndentationAtPosition(file, position, editorOptions); - var spaces = generateSpaces(indentPosition); - if (indentPosition > 0) { - edits.push({ span: ts.createTextSpanFromBounds(position, position), newText: spaces }); + var scriptInfo = compilerService.host.getScriptInfo(file); + if (scriptInfo) { + var lineInfo = scriptInfo.getLineInfo(line); + if (lineInfo && (lineInfo.leaf) && (lineInfo.leaf.text)) { + var lineText = lineInfo.leaf.text; + if (lineText.search("\\S") < 0) { + // TODO: get these options from host + var editorOptions: ts.EditorOptions = { + IndentSize: 4, + TabSize: 4, + NewLineCharacter: "\n", + ConvertTabsToSpaces: true, + }; + var indentPosition = + compilerService.languageService.getIndentationAtPosition(file, position, editorOptions); + for (var i = 0, len = lineText.length; i < len; i++) { + if (lineText.charAt(i) == " ") { + indentPosition--; + } + else { + break; + } + } + if (indentPosition > 0) { + var spaces = generateSpaces(indentPosition); + edits.push({ span: ts.createTextSpanFromBounds(position, position), newText: spaces }); + } + else if (indentPosition < 0) { + edits.push({ + span: ts.createTextSpanFromBounds(position, position - indentPosition), + newText: "" + }); + } + } + } } } @@ -491,7 +524,7 @@ module ts.server { }; }); } - + getCompletions(line: number, col: number, prefix: string, fileName: string): protocol.CompletionEntry[] { if (!prefix) { prefix = ""; @@ -693,6 +726,10 @@ module ts.server { } onMessage(message: string) { + if (this.logger.isVerbose()) { + this.logger.info("request: " + message); + var start = process.hrtime(); + } try { var request = JSON.parse(message); var response: any; @@ -798,13 +835,23 @@ module ts.server { } } + if (this.logger.isVerbose()) { + var elapsed = process.hrtime(start); + var seconds = elapsed[0] + var nanoseconds = elapsed[1]; + var elapsedMs = ((1e9 * seconds) + nanoseconds)/1000000.0; + var leader = "Elapsed time (in milliseconds)"; + if (!responseRequired) { + leader = "Async elapsed time (in milliseconds)"; + } + this.logger.msg(leader + ": " + elapsedMs.toFixed(4).toString(), "Perf"); + } if (response) { this.output(response, request.command, request.seq); } else if (responseRequired) { this.output(undefined, request.command, request.seq, "No content available."); } - } catch (err) { if (err instanceof OperationCanceledException) { // Handle cancellation exceptions