Skip to content

Commit 06d29a0

Browse files
committed
Breakpoint span in variable declarations in new language service
Also updates the fourslash breakpoints baseline to be more readable
1 parent a6eb698 commit 06d29a0

File tree

6 files changed

+183
-14
lines changed

6 files changed

+183
-14
lines changed

Jakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var servicesSources = [
5555
].map(function (f) {
5656
return path.join(compilerDirectory, f);
5757
}).concat([
58+
"breakpoints.ts",
5859
"services.ts",
5960
"shims.ts",
6061
"signatureHelp.ts",

src/harness/fourslash.ts

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -985,13 +985,23 @@ module FourSlash {
985985
return item.parameters[currentParam];
986986
}
987987

988-
public getBreakpointStatementLocation(pos: number) {
988+
private alignmentForExtraInfo = 50;
989+
990+
public getBreakpointStatementLocation(pos: number, prefixString: string) {
989991
this.taoInvalidReason = 'getBreakpointStatementLocation NYI';
990992

991993
var spanInfo = this.languageService.getBreakpointStatementAtPosition(this.activeFile.fileName, pos);
992-
var resultString = "\n**Pos: " + pos + " SpanInfo: " + JSON.stringify(spanInfo) + "\n** Statement: ";
993-
if (spanInfo !== null) {
994-
resultString = resultString + this.activeFile.content.substr(spanInfo.start(), spanInfo.length());
994+
var resultString = "SpanInfo: " + JSON.stringify(spanInfo);
995+
if (spanInfo) {
996+
var spanString = this.activeFile.content.substr(spanInfo.start(), spanInfo.length());
997+
var spanLineMap = ts.getLineStarts(spanString);
998+
for (var i = 0; i < spanLineMap.length; i++) {
999+
if (!i) {
1000+
resultString += "\n";
1001+
}
1002+
resultString += prefixString + spanString.substring(spanLineMap[i], spanLineMap[i + 1]);
1003+
}
1004+
resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start()) + ") to (" + this.getLineColStringAtPosition(spanInfo.end()) + ")";
9951005
}
9961006
return resultString;
9971007
}
@@ -1003,12 +1013,60 @@ module FourSlash {
10031013
"Breakpoint Locations for " + this.activeFile.fileName,
10041014
this.testData.globalOptions[testOptMetadataNames.baselineFile],
10051015
() => {
1006-
var fileLength = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getLength();
1016+
var fileLineMap = ts.getLineStarts(this.activeFile.content);
1017+
var nextLine = 0;
10071018
var resultString = "";
1008-
for (var pos = 0; pos < fileLength; pos++) {
1009-
resultString = resultString + this.getBreakpointStatementLocation(pos);
1019+
var currentLine: string;
1020+
var previousSpanInfo: string;
1021+
var startColumn: number;
1022+
var length: number;
1023+
var prefixString = " >";
1024+
1025+
var addSpanInfoString = () => {
1026+
if (previousSpanInfo) {
1027+
resultString += currentLine;
1028+
var thisLineMarker = repeatString(startColumn, " ") + repeatString(length, "~");
1029+
thisLineMarker += repeatString(this.alignmentForExtraInfo - thisLineMarker.length - prefixString.length + 1, " ");
1030+
resultString += thisLineMarker;
1031+
resultString += "=> Pos: (" + (pos - length) + " to " + (pos - 1) + ") ";
1032+
resultString += " " + previousSpanInfo;
1033+
previousSpanInfo = undefined;
1034+
}
1035+
};
1036+
1037+
for (var pos = 0; pos < this.activeFile.content.length; pos++) {
1038+
if (pos === 0 || pos === fileLineMap[nextLine]) {
1039+
nextLine++;
1040+
addSpanInfoString();
1041+
if (resultString.length) {
1042+
resultString += "\n--------------------------------";
1043+
}
1044+
currentLine = "\n" + nextLine.toString() + repeatString(3 - nextLine.toString().length, " ") + ">" + this.activeFile.content.substring(pos, fileLineMap[nextLine]) + "\n ";
1045+
startColumn = 0;
1046+
length = 0;
1047+
}
1048+
var spanInfo = this.getBreakpointStatementLocation(pos, prefixString);
1049+
if (previousSpanInfo && previousSpanInfo !== spanInfo) {
1050+
addSpanInfoString();
1051+
previousSpanInfo = spanInfo;
1052+
startColumn = startColumn + length;
1053+
length = 1;
1054+
}
1055+
else {
1056+
previousSpanInfo = spanInfo;
1057+
length++;
1058+
}
10101059
}
1060+
addSpanInfoString();
10111061
return resultString;
1062+
1063+
function repeatString(count: number, char: string) {
1064+
var result = "";
1065+
for (var i = 0; i < count; i++) {
1066+
result += char;
1067+
}
1068+
return result;
1069+
}
10121070
},
10131071
true /* run immediately */);
10141072
}
@@ -1056,7 +1114,7 @@ module FourSlash {
10561114
}
10571115

10581116
public printBreakpointLocation(pos: number) {
1059-
Harness.IO.log(this.getBreakpointStatementLocation(pos));
1117+
Harness.IO.log("\n**Pos: " + pos + " " + this.getBreakpointStatementLocation(pos, " "));
10601118
}
10611119

10621120
public printBreakpointAtCurrentLocation() {
@@ -1502,7 +1560,7 @@ module FourSlash {
15021560
throw new Error('verifyCaretAtMarker failed - expected to be in file "' + pos.fileName + '", but was in file "' + this.activeFile.fileName + '"');
15031561
}
15041562
if (pos.position !== this.currentCaretPosition) {
1505-
throw new Error('verifyCaretAtMarker failed - expected to be at marker "/*' + markerName + '*/, but was at position ' + this.currentCaretPosition + '(' + this.getLineColStringAtCaret() + ')');
1563+
throw new Error('verifyCaretAtMarker failed - expected to be at marker "/*' + markerName + '*/, but was at position ' + this.currentCaretPosition + '(' + this.getLineColStringAtPosition(this.currentCaretPosition) + ')');
15061564
}
15071565
}
15081566

@@ -2102,8 +2160,8 @@ module FourSlash {
21022160
return this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition).line + 1;
21032161
}
21042162

2105-
private getLineColStringAtCaret() {
2106-
var pos = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition);
2163+
private getLineColStringAtPosition(position: number) {
2164+
var pos = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, position);
21072165
return 'line ' + (pos.line + 1) + ', col ' + pos.character;
21082166
}
21092167

src/services/breakpoints.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,4 +1082,93 @@ module TypeScript.Services.Breakpoints {
10821082
var breakpointResolver = new BreakpointResolver(posLine, lineMap);
10831083
return breakpointResolver.breakpointSpanOf(positionedToken);
10841084
}
1085+
}
1086+
1087+
module ts.Breakpoints {
1088+
/**
1089+
* Get the breakpoint span in given sourceFile
1090+
*/
1091+
export function spanInSourceFileAtLocation(sourceFile: SourceFile, position: number) {
1092+
// Cannot set breakpoint in dts file
1093+
if (sourceFile.flags & NodeFlags.DeclarationFile) {
1094+
return;
1095+
}
1096+
1097+
var tokenAtLocation = getTokenAtPosition(sourceFile, position);
1098+
var lineOfPosition = sourceFile.getLineAndCharacterFromPosition(position).line;
1099+
if (sourceFile.getLineAndCharacterFromPosition(tokenAtLocation.getStart()).line > lineOfPosition) {
1100+
// Get previous token if the token is returned starts on new line
1101+
// eg: var x =10; |--- curser is here
1102+
// var y = 10;
1103+
// token at position will return var keyword on second line as the token but we would like to use
1104+
// token on same line if trailing trivia (comments or white spaces on same line) part of the last token on that line
1105+
tokenAtLocation = findPrecedingToken(tokenAtLocation.pos, sourceFile);
1106+
}
1107+
1108+
// Cannot set breakpoint in ambient declarations
1109+
if (isInAmbientContext(tokenAtLocation)) {
1110+
return;
1111+
}
1112+
1113+
// Get the span in the node based on its syntax
1114+
return spanInNode(tokenAtLocation);
1115+
1116+
function textSpan(startNode: Node, endNode?: Node) {
1117+
return TypeScript.TextSpan.fromBounds(startNode.getStart(), (endNode || startNode).getEnd());
1118+
}
1119+
1120+
function spanInNodeIfStartsOnSameLine(node: Node): TypeScript.TextSpan {
1121+
if (node && sourceFile.getLineAndCharacterFromPosition(position).line === sourceFile.getLineAndCharacterFromPosition(node.getStart()).line) {
1122+
return spanInNode(node);
1123+
}
1124+
}
1125+
1126+
function spanInNode(node: Node): TypeScript.TextSpan {
1127+
if (node) {
1128+
switch (node.kind) {
1129+
case SyntaxKind.VariableStatement:
1130+
return spanInVariableStatement(<VariableStatement>node);
1131+
1132+
case SyntaxKind.VariableDeclaration:
1133+
return spanInVariableDeclaration(<VariableDeclaration>node);
1134+
1135+
case SyntaxKind.SemicolonToken:
1136+
case SyntaxKind.EndOfFileToken:
1137+
return spanInNodeIfStartsOnSameLine(findPrecedingToken(node.pos, sourceFile));
1138+
1139+
default:
1140+
// Default go to parent to set the breakpoint
1141+
return spanInNode(node.parent);
1142+
}
1143+
}
1144+
1145+
function spanInVariableDeclaration(variableDeclaration: VariableDeclaration): TypeScript.TextSpan {
1146+
var isParentVariableStatement = variableDeclaration.parent.kind === SyntaxKind.VariableStatement;
1147+
var isfirstDeclarationOfVariableStatement = isParentVariableStatement &&
1148+
(<VariableStatement>variableDeclaration.parent).declarations[0] === variableDeclaration;
1149+
1150+
// Breakpoint is possible in variableDeclaration only if there is initialization
1151+
if (variableDeclaration.initializer) {
1152+
if (isfirstDeclarationOfVariableStatement) {
1153+
// First declaration - include var keyword
1154+
return textSpan(variableDeclaration.parent, variableDeclaration);
1155+
}
1156+
else {
1157+
// Span only on this declaration
1158+
return textSpan(variableDeclaration);
1159+
}
1160+
}
1161+
else if (!isfirstDeclarationOfVariableStatement && isParentVariableStatement) {
1162+
// If we cant set breakpoint on this declaration, set it on previous one
1163+
var variableStatement = <VariableStatement>variableDeclaration.parent;
1164+
var indexOfCurrentDeclaration = indexOf(variableStatement.declarations, variableDeclaration);
1165+
return spanInVariableDeclaration(variableStatement.declarations[indexOfCurrentDeclaration - 1]);
1166+
}
1167+
}
1168+
1169+
function spanInVariableStatement(variableStatement: VariableStatement): TypeScript.TextSpan {
1170+
return spanInVariableDeclaration(variableStatement.declarations[0]);
1171+
}
1172+
}
1173+
}
10851174
}

src/services/services.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4737,9 +4737,7 @@ module ts {
47374737
function getBreakpointStatementAtPosition(filename: string, position: number) {
47384738
// doesn't use compiler - no need to synchronize with host
47394739
filename = TypeScript.switchToForwardSlashes(filename);
4740-
4741-
var syntaxtree = getSyntaxTree(filename);
4742-
return TypeScript.Services.Breakpoints.getBreakpointLocation(syntaxtree, position);
4740+
return Breakpoints.spanInSourceFileAtLocation(getCurrentSourceFile(filename), position);
47434741
}
47444742

47454743
function getNavigationBarItems(filename: string): NavigationBarItem[] {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
1 >var a = 10;
3+
4+
~~~~~~~~~~~~ => Pos: (0 to 11) SpanInfo: {"start":0,"length":10}
5+
>var a = 10
6+
>:=> (line 1, col 0) to (line 1, col 10)
7+
--------------------------------
8+
2 >var b;
9+
10+
~~~~~~~ => Pos: (12 to 18) SpanInfo: undefined
11+
--------------------------------
12+
3 >var c = 10, d, e;
13+
14+
~~~~~~~~~~~~~~~~~~ => Pos: (19 to 36) SpanInfo: {"start":19,"length":10}
15+
>var c = 10
16+
>:=> (line 3, col 0) to (line 3, col 10)
17+
--------------------------------
18+
4 >var c2, d2 = 10;
19+
~~~~~~~ => Pos: (37 to 43) SpanInfo: undefined
20+
4 >var c2, d2 = 10;
21+
~~~~~~~~~ => Pos: (44 to 52) SpanInfo: {"start":45,"length":7}
22+
>d2 = 10
23+
>:=> (line 4, col 8) to (line 4, col 15)

0 commit comments

Comments
 (0)