From 06d29a00f2124e727627d4397ae2ea4a378475d2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 17 Oct 2014 15:39:31 -0700 Subject: [PATCH] Breakpoint span in variable declarations in new language service Also updates the fourslash breakpoints baseline to be more readable --- Jakefile | 1 + src/harness/fourslash.ts | 80 ++++++++++++++--- src/services/breakpoints.ts | 89 +++++++++++++++++++ src/services/services.ts | 4 +- .../reference/bpSpan_variables.baseline | 23 +++++ .../breakpointValidationVariables.ts | 0 6 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 tests/baselines/reference/bpSpan_variables.baseline rename tests/cases/{fourslash_old => fourslash}/breakpointValidationVariables.ts (100%) diff --git a/Jakefile b/Jakefile index 34879a56691..64394c2d60e 100644 --- a/Jakefile +++ b/Jakefile @@ -55,6 +55,7 @@ var servicesSources = [ ].map(function (f) { return path.join(compilerDirectory, f); }).concat([ + "breakpoints.ts", "services.ts", "shims.ts", "signatureHelp.ts", diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 9dad3dddf60..ba7c6115812 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -985,13 +985,23 @@ module FourSlash { return item.parameters[currentParam]; } - public getBreakpointStatementLocation(pos: number) { + private alignmentForExtraInfo = 50; + + public getBreakpointStatementLocation(pos: number, prefixString: string) { this.taoInvalidReason = 'getBreakpointStatementLocation NYI'; var spanInfo = this.languageService.getBreakpointStatementAtPosition(this.activeFile.fileName, pos); - var resultString = "\n**Pos: " + pos + " SpanInfo: " + JSON.stringify(spanInfo) + "\n** Statement: "; - if (spanInfo !== null) { - resultString = resultString + this.activeFile.content.substr(spanInfo.start(), spanInfo.length()); + var resultString = "SpanInfo: " + JSON.stringify(spanInfo); + if (spanInfo) { + var spanString = this.activeFile.content.substr(spanInfo.start(), spanInfo.length()); + var spanLineMap = ts.getLineStarts(spanString); + for (var i = 0; i < spanLineMap.length; i++) { + if (!i) { + resultString += "\n"; + } + resultString += prefixString + spanString.substring(spanLineMap[i], spanLineMap[i + 1]); + } + resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start()) + ") to (" + this.getLineColStringAtPosition(spanInfo.end()) + ")"; } return resultString; } @@ -1003,12 +1013,60 @@ module FourSlash { "Breakpoint Locations for " + this.activeFile.fileName, this.testData.globalOptions[testOptMetadataNames.baselineFile], () => { - var fileLength = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getLength(); + var fileLineMap = ts.getLineStarts(this.activeFile.content); + var nextLine = 0; var resultString = ""; - for (var pos = 0; pos < fileLength; pos++) { - resultString = resultString + this.getBreakpointStatementLocation(pos); + var currentLine: string; + var previousSpanInfo: string; + var startColumn: number; + var length: number; + var prefixString = " >"; + + var addSpanInfoString = () => { + if (previousSpanInfo) { + resultString += currentLine; + var thisLineMarker = repeatString(startColumn, " ") + repeatString(length, "~"); + thisLineMarker += repeatString(this.alignmentForExtraInfo - thisLineMarker.length - prefixString.length + 1, " "); + resultString += thisLineMarker; + resultString += "=> Pos: (" + (pos - length) + " to " + (pos - 1) + ") "; + resultString += " " + previousSpanInfo; + previousSpanInfo = undefined; + } + }; + + for (var pos = 0; pos < this.activeFile.content.length; pos++) { + if (pos === 0 || pos === fileLineMap[nextLine]) { + nextLine++; + addSpanInfoString(); + if (resultString.length) { + resultString += "\n--------------------------------"; + } + currentLine = "\n" + nextLine.toString() + repeatString(3 - nextLine.toString().length, " ") + ">" + this.activeFile.content.substring(pos, fileLineMap[nextLine]) + "\n "; + startColumn = 0; + length = 0; + } + var spanInfo = this.getBreakpointStatementLocation(pos, prefixString); + if (previousSpanInfo && previousSpanInfo !== spanInfo) { + addSpanInfoString(); + previousSpanInfo = spanInfo; + startColumn = startColumn + length; + length = 1; + } + else { + previousSpanInfo = spanInfo; + length++; + } } + addSpanInfoString(); return resultString; + + function repeatString(count: number, char: string) { + var result = ""; + for (var i = 0; i < count; i++) { + result += char; + } + return result; + } }, true /* run immediately */); } @@ -1056,7 +1114,7 @@ module FourSlash { } public printBreakpointLocation(pos: number) { - Harness.IO.log(this.getBreakpointStatementLocation(pos)); + Harness.IO.log("\n**Pos: " + pos + " " + this.getBreakpointStatementLocation(pos, " ")); } public printBreakpointAtCurrentLocation() { @@ -1502,7 +1560,7 @@ module FourSlash { throw new Error('verifyCaretAtMarker failed - expected to be in file "' + pos.fileName + '", but was in file "' + this.activeFile.fileName + '"'); } if (pos.position !== this.currentCaretPosition) { - throw new Error('verifyCaretAtMarker failed - expected to be at marker "/*' + markerName + '*/, but was at position ' + this.currentCaretPosition + '(' + this.getLineColStringAtCaret() + ')'); + throw new Error('verifyCaretAtMarker failed - expected to be at marker "/*' + markerName + '*/, but was at position ' + this.currentCaretPosition + '(' + this.getLineColStringAtPosition(this.currentCaretPosition) + ')'); } } @@ -2102,8 +2160,8 @@ module FourSlash { return this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition).line + 1; } - private getLineColStringAtCaret() { - var pos = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition); + private getLineColStringAtPosition(position: number) { + var pos = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, position); return 'line ' + (pos.line + 1) + ', col ' + pos.character; } diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 923458c144d..7eb44213e02 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -1082,4 +1082,93 @@ module TypeScript.Services.Breakpoints { var breakpointResolver = new BreakpointResolver(posLine, lineMap); return breakpointResolver.breakpointSpanOf(positionedToken); } +} + +module ts.Breakpoints { + /** + * Get the breakpoint span in given sourceFile + */ + export function spanInSourceFileAtLocation(sourceFile: SourceFile, position: number) { + // Cannot set breakpoint in dts file + if (sourceFile.flags & NodeFlags.DeclarationFile) { + return; + } + + var tokenAtLocation = getTokenAtPosition(sourceFile, position); + var lineOfPosition = sourceFile.getLineAndCharacterFromPosition(position).line; + if (sourceFile.getLineAndCharacterFromPosition(tokenAtLocation.getStart()).line > lineOfPosition) { + // Get previous token if the token is returned starts on new line + // eg: var x =10; |--- curser is here + // var y = 10; + // token at position will return var keyword on second line as the token but we would like to use + // token on same line if trailing trivia (comments or white spaces on same line) part of the last token on that line + tokenAtLocation = findPrecedingToken(tokenAtLocation.pos, sourceFile); + } + + // Cannot set breakpoint in ambient declarations + if (isInAmbientContext(tokenAtLocation)) { + return; + } + + // Get the span in the node based on its syntax + return spanInNode(tokenAtLocation); + + function textSpan(startNode: Node, endNode?: Node) { + return TypeScript.TextSpan.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); + } + + function spanInNodeIfStartsOnSameLine(node: Node): TypeScript.TextSpan { + if (node && sourceFile.getLineAndCharacterFromPosition(position).line === sourceFile.getLineAndCharacterFromPosition(node.getStart()).line) { + return spanInNode(node); + } + } + + function spanInNode(node: Node): TypeScript.TextSpan { + if (node) { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return spanInVariableStatement(node); + + case SyntaxKind.VariableDeclaration: + return spanInVariableDeclaration(node); + + case SyntaxKind.SemicolonToken: + case SyntaxKind.EndOfFileToken: + return spanInNodeIfStartsOnSameLine(findPrecedingToken(node.pos, sourceFile)); + + default: + // Default go to parent to set the breakpoint + return spanInNode(node.parent); + } + } + + function spanInVariableDeclaration(variableDeclaration: VariableDeclaration): TypeScript.TextSpan { + var isParentVariableStatement = variableDeclaration.parent.kind === SyntaxKind.VariableStatement; + var isfirstDeclarationOfVariableStatement = isParentVariableStatement && + (variableDeclaration.parent).declarations[0] === variableDeclaration; + + // Breakpoint is possible in variableDeclaration only if there is initialization + if (variableDeclaration.initializer) { + if (isfirstDeclarationOfVariableStatement) { + // First declaration - include var keyword + return textSpan(variableDeclaration.parent, variableDeclaration); + } + else { + // Span only on this declaration + return textSpan(variableDeclaration); + } + } + else if (!isfirstDeclarationOfVariableStatement && isParentVariableStatement) { + // If we cant set breakpoint on this declaration, set it on previous one + var variableStatement = variableDeclaration.parent; + var indexOfCurrentDeclaration = indexOf(variableStatement.declarations, variableDeclaration); + return spanInVariableDeclaration(variableStatement.declarations[indexOfCurrentDeclaration - 1]); + } + } + + function spanInVariableStatement(variableStatement: VariableStatement): TypeScript.TextSpan { + return spanInVariableDeclaration(variableStatement.declarations[0]); + } + } + } } \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 5411cac8d0c..4a22d486219 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4737,9 +4737,7 @@ module ts { function getBreakpointStatementAtPosition(filename: string, position: number) { // doesn't use compiler - no need to synchronize with host filename = TypeScript.switchToForwardSlashes(filename); - - var syntaxtree = getSyntaxTree(filename); - return TypeScript.Services.Breakpoints.getBreakpointLocation(syntaxtree, position); + return Breakpoints.spanInSourceFileAtLocation(getCurrentSourceFile(filename), position); } function getNavigationBarItems(filename: string): NavigationBarItem[] { diff --git a/tests/baselines/reference/bpSpan_variables.baseline b/tests/baselines/reference/bpSpan_variables.baseline new file mode 100644 index 00000000000..1dbb80512f5 --- /dev/null +++ b/tests/baselines/reference/bpSpan_variables.baseline @@ -0,0 +1,23 @@ + +1 >var a = 10; + + ~~~~~~~~~~~~ => Pos: (0 to 11) SpanInfo: {"start":0,"length":10} + >var a = 10 + >:=> (line 1, col 0) to (line 1, col 10) +-------------------------------- +2 >var b; + + ~~~~~~~ => Pos: (12 to 18) SpanInfo: undefined +-------------------------------- +3 >var c = 10, d, e; + + ~~~~~~~~~~~~~~~~~~ => Pos: (19 to 36) SpanInfo: {"start":19,"length":10} + >var c = 10 + >:=> (line 3, col 0) to (line 3, col 10) +-------------------------------- +4 >var c2, d2 = 10; + ~~~~~~~ => Pos: (37 to 43) SpanInfo: undefined +4 >var c2, d2 = 10; + ~~~~~~~~~ => Pos: (44 to 52) SpanInfo: {"start":45,"length":7} + >d2 = 10 + >:=> (line 4, col 8) to (line 4, col 15) \ No newline at end of file diff --git a/tests/cases/fourslash_old/breakpointValidationVariables.ts b/tests/cases/fourslash/breakpointValidationVariables.ts similarity index 100% rename from tests/cases/fourslash_old/breakpointValidationVariables.ts rename to tests/cases/fourslash/breakpointValidationVariables.ts