Merge pull request #6019 from Microsoft/sourceMapDestructuring

Fixes for source map of destructuring downlevel syntax
This commit is contained in:
Mohamed Hegazy
2016-01-11 14:36:28 -08:00
327 changed files with 101545 additions and 1224 deletions

View File

@@ -464,8 +464,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
const writer = createTextWriter(newLine);
const { write, writeTextOfNode, writeLine, increaseIndent, decreaseIndent } = writer;
const sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? createSourceMapWriter(host, writer) : getNullSourceMapWriter();
const { setSourceFile, emitStart, emitEnd, emitPos } = sourceMap;
let sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? createSourceMapWriter(host, writer) : getNullSourceMapWriter();
let { setSourceFile, emitStart, emitEnd, emitPos } = sourceMap;
let currentSourceFile: SourceFile;
let currentText: string;
@@ -512,6 +512,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
/** If removeComments is true, no leading-comments needed to be emitted **/
const emitLeadingCommentsOfPosition = compilerOptions.removeComments ? function (pos: number) { } : emitLeadingCommentsOfPositionWorker;
const setSourceMapWriterEmit = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? changeSourceMapEmit : function (writer: SourceMapWriter) { };
const moduleEmitDelegates: Map<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = {
[ModuleKind.ES6]: emitES6Module,
[ModuleKind.AMD]: emitAMDModule,
@@ -1975,7 +1977,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
result.expression = parenthesizeForAccess(expression);
result.dotToken = createSynthesizedNode(SyntaxKind.DotToken);
result.name = name;
return result;
}
@@ -2568,7 +2569,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
leftHandSideExpression.argumentExpression.kind !== SyntaxKind.StringLiteral) {
const tempArgumentExpression = createAndRecordTempVariable(TempFlags._i);
(<ElementAccessExpression>synthesizedLHS).argumentExpression = tempArgumentExpression;
emitAssignment(tempArgumentExpression, leftHandSideExpression.argumentExpression, /*shouldEmitCommaBeforeAssignment*/ true);
emitAssignment(tempArgumentExpression, leftHandSideExpression.argumentExpression, /*shouldEmitCommaBeforeAssignment*/ true, leftHandSideExpression.expression);
}
else {
(<ElementAccessExpression>synthesizedLHS).argumentExpression = leftHandSideExpression.argumentExpression;
@@ -2796,7 +2797,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
* Returns false if nothing was written - this can happen for source file level variable declarations
* in system modules where such variable declarations are hoisted.
*/
function tryEmitStartOfVariableDeclarationList(decl: VariableDeclarationList, startPos?: number): boolean {
function tryEmitStartOfVariableDeclarationList(decl: VariableDeclarationList): boolean {
if (shouldHoistVariable(decl, /*checkIfSourceFileLevelDecl*/ true)) {
// variables in variable declaration list were already hoisted
return false;
@@ -2811,34 +2812,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
return false;
}
let tokenKind = SyntaxKind.VarKeyword;
emitStart(decl);
if (decl && languageVersion >= ScriptTarget.ES6) {
if (isLet(decl)) {
tokenKind = SyntaxKind.LetKeyword;
write("let ");
}
else if (isConst(decl)) {
tokenKind = SyntaxKind.ConstKeyword;
write("const ");
}
else {
write("var ");
}
}
if (startPos !== undefined) {
emitToken(tokenKind, startPos);
write(" ");
}
else {
switch (tokenKind) {
case SyntaxKind.VarKeyword:
write("var ");
break;
case SyntaxKind.LetKeyword:
write("let ");
break;
case SyntaxKind.ConstKeyword:
write("const ");
break;
}
write("var ");
}
// Note here we specifically dont emit end so that if we are going to emit binding pattern
// we can alter the source map correctly
return true;
}
@@ -3177,7 +3167,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
endPos = emitToken(SyntaxKind.OpenParenToken, endPos);
if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) {
const variableDeclarationList = <VariableDeclarationList>node.initializer;
const startIsEmitted = tryEmitStartOfVariableDeclarationList(variableDeclarationList, endPos);
const startIsEmitted = tryEmitStartOfVariableDeclarationList(variableDeclarationList);
if (startIsEmitted) {
emitCommaList(variableDeclarationList.declarations);
}
@@ -3218,7 +3208,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
if (node.initializer.kind === SyntaxKind.VariableDeclarationList) {
const variableDeclarationList = <VariableDeclarationList>node.initializer;
if (variableDeclarationList.declarations.length >= 1) {
tryEmitStartOfVariableDeclarationList(variableDeclarationList, endPos);
tryEmitStartOfVariableDeclarationList(variableDeclarationList);
emit(variableDeclarationList.declarations[0]);
}
}
@@ -3305,21 +3295,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
write("; ");
// _i < _a.length;
emitStart(node.initializer);
emitStart(node.expression);
emitNodeWithoutSourceMap(counter);
write(" < ");
emitNodeWithCommentsAndWithoutSourcemap(rhsReference);
write(".length");
emitEnd(node.initializer);
emitEnd(node.expression);
write("; ");
// _i++)
emitStart(node.initializer);
emitStart(node.expression);
emitNodeWithoutSourceMap(counter);
write("++");
emitEnd(node.initializer);
emitEnd(node.expression);
emitToken(SyntaxKind.CloseParenToken, node.expression.end);
// Body
@@ -3719,7 +3709,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
* @param value an expression as a right-hand-side operand of the assignment
* @param shouldEmitCommaBeforeAssignment a boolean indicating whether to prefix an assignment with comma
*/
function emitAssignment(name: Identifier, value: Expression, shouldEmitCommaBeforeAssignment: boolean) {
function emitAssignment(name: Identifier, value: Expression, shouldEmitCommaBeforeAssignment: boolean, nodeForSourceMap: Node) {
if (shouldEmitCommaBeforeAssignment) {
write(", ");
}
@@ -3735,15 +3725,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
const isVariableDeclarationOrBindingElement =
name.parent && (name.parent.kind === SyntaxKind.VariableDeclaration || name.parent.kind === SyntaxKind.BindingElement);
if (isVariableDeclarationOrBindingElement) {
emitModuleMemberName(<Declaration>name.parent);
}
else {
emit(name);
}
// If this is first var declaration, we need to start at var/let/const keyword instead
// otherwise use nodeForSourceMap as the start position
emitStart(isFirstVariableDeclaration(nodeForSourceMap) ? nodeForSourceMap.parent : nodeForSourceMap);
withTemporaryNoSourceMap(() => {
if (isVariableDeclarationOrBindingElement) {
emitModuleMemberName(<Declaration>name.parent);
}
else {
emit(name);
}
write(" = ");
emit(value);
write(" = ");
emit(value);
});
emitEnd(nodeForSourceMap, /*stopOverridingSpan*/true);
if (exportChanged) {
write(")");
@@ -3756,15 +3752,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
* @param canDefineTempVariablesInPlace a boolean indicating whether you can define the temporary variable at an assignment location
* @param shouldEmitCommaBeforeAssignment a boolean indicating whether an assignment should prefix with comma
*/
function emitTempVariableAssignment(expression: Expression, canDefineTempVariablesInPlace: boolean, shouldEmitCommaBeforeAssignment: boolean): Identifier {
function emitTempVariableAssignment(expression: Expression, canDefineTempVariablesInPlace: boolean, shouldEmitCommaBeforeAssignment: boolean, sourceMapNode?: Node): Identifier {
const identifier = createTempVariable(TempFlags.Auto);
if (!canDefineTempVariablesInPlace) {
recordTempDeclaration(identifier);
}
emitAssignment(identifier, expression, shouldEmitCommaBeforeAssignment);
emitAssignment(identifier, expression, shouldEmitCommaBeforeAssignment, sourceMapNode || expression.parent);
return identifier;
}
function isFirstVariableDeclaration(root: Node) {
return root.kind === SyntaxKind.VariableDeclaration &&
root.parent.kind === SyntaxKind.VariableDeclarationList &&
(<VariableDeclarationList>root.parent).declarations[0] === root;
}
function emitDestructuring(root: BinaryExpression | VariableDeclaration | ParameterDeclaration, isAssignmentExpressionStatement: boolean, value?: Expression) {
let emitCount = 0;
@@ -3787,6 +3789,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
}
else {
Debug.assert(!isAssignmentExpressionStatement);
// If first variable declaration of variable statement correct the start location
if (isFirstVariableDeclaration(root)) {
// Use emit location of "var " as next emit start entry
sourceMap.changeEmitSourcePos();
}
emitBindingElement(<BindingElement>root, value);
}
@@ -3800,20 +3807,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
* @param reuseIdentifierExpressions true if identifier expressions can simply be returned;
* false if it is necessary to always emit an identifier.
*/
function ensureIdentifier(expr: Expression, reuseIdentifierExpressions: boolean): Expression {
function ensureIdentifier(expr: Expression, reuseIdentifierExpressions: boolean, sourceMapNode: Node): Expression {
if (expr.kind === SyntaxKind.Identifier && reuseIdentifierExpressions) {
return expr;
}
const identifier = emitTempVariableAssignment(expr, canDefineTempVariablesInPlace, emitCount > 0);
const identifier = emitTempVariableAssignment(expr, canDefineTempVariablesInPlace, emitCount > 0, sourceMapNode);
emitCount++;
return identifier;
}
function createDefaultValueCheck(value: Expression, defaultValue: Expression): Expression {
function createDefaultValueCheck(value: Expression, defaultValue: Expression, sourceMapNode: Node): Expression {
// The value expression will be evaluated twice, so for anything but a simple identifier
// we need to generate a temporary variable
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true);
// If the temporary variable needs to be emitted use the source Map node for assignment of that statement
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, sourceMapNode);
// Return the expression 'value === void 0 ? defaultValue : value'
const equals = <BinaryExpression>createSynthesizedNode(SyntaxKind.BinaryExpression);
equals.left = value;
@@ -3842,7 +3850,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
let index: Expression;
const nameIsComputed = propName.kind === SyntaxKind.ComputedPropertyName;
if (nameIsComputed) {
index = ensureIdentifier((<ComputedPropertyName>propName).expression, /*reuseIdentifierExpressions*/ false);
// TODO to handle when we look into sourcemaps for computed properties, for now use propName
index = ensureIdentifier((<ComputedPropertyName>propName).expression, /*reuseIdentifierExpressions*/ false, propName);
}
else {
// We create a synthetic copy of the identifier in order to avoid the rewriting that might
@@ -3866,61 +3875,66 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
return call;
}
function emitObjectLiteralAssignment(target: ObjectLiteralExpression, value: Expression) {
function emitObjectLiteralAssignment(target: ObjectLiteralExpression, value: Expression, sourceMapNode: Node) {
const properties = target.properties;
if (properties.length !== 1) {
// For anything but a single element destructuring we need to generate a temporary
// to ensure value is evaluated exactly once.
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true);
// When doing so we want to hightlight the passed in source map node since thats the one needing this temp assignment
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, sourceMapNode);
}
for (const p of properties) {
if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) {
const propName = <Identifier | LiteralExpression>(<PropertyAssignment>p).name;
const target = p.kind === SyntaxKind.ShorthandPropertyAssignment ? <ShorthandPropertyAssignment>p : (<PropertyAssignment>p).initializer || propName;
emitDestructuringAssignment(target, createPropertyAccessForDestructuringProperty(value, propName));
// Assignment for target = value.propName should highligh whole property, hence use p as source map node
emitDestructuringAssignment(target, createPropertyAccessForDestructuringProperty(value, propName), p);
}
}
}
function emitArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression) {
function emitArrayLiteralAssignment(target: ArrayLiteralExpression, value: Expression, sourceMapNode: Node) {
const elements = target.elements;
if (elements.length !== 1) {
// For anything but a single element destructuring we need to generate a temporary
// to ensure value is evaluated exactly once.
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true);
// When doing so we want to hightlight the passed in source map node since thats the one needing this temp assignment
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, sourceMapNode);
}
for (let i = 0; i < elements.length; i++) {
const e = elements[i];
if (e.kind !== SyntaxKind.OmittedExpression) {
// Assignment for target = value.propName should highligh whole property, hence use e as source map node
if (e.kind !== SyntaxKind.SpreadElementExpression) {
emitDestructuringAssignment(e, createElementAccessExpression(value, createNumericLiteral(i)));
emitDestructuringAssignment(e, createElementAccessExpression(value, createNumericLiteral(i)), e);
}
else if (i === elements.length - 1) {
emitDestructuringAssignment((<SpreadElementExpression>e).expression, createSliceCall(value, i));
emitDestructuringAssignment((<SpreadElementExpression>e).expression, createSliceCall(value, i), e);
}
}
}
}
function emitDestructuringAssignment(target: Expression | ShorthandPropertyAssignment, value: Expression) {
function emitDestructuringAssignment(target: Expression | ShorthandPropertyAssignment, value: Expression, sourceMapNode: Node) {
// When emitting target = value use source map node to highlight, including any temporary assignments needed for this
if (target.kind === SyntaxKind.ShorthandPropertyAssignment) {
if ((<ShorthandPropertyAssignment>target).objectAssignmentInitializer) {
value = createDefaultValueCheck(value, (<ShorthandPropertyAssignment>target).objectAssignmentInitializer);
value = createDefaultValueCheck(value, (<ShorthandPropertyAssignment>target).objectAssignmentInitializer, sourceMapNode);
}
target = (<ShorthandPropertyAssignment>target).name;
}
else if (target.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>target).operatorToken.kind === SyntaxKind.EqualsToken) {
value = createDefaultValueCheck(value, (<BinaryExpression>target).right);
value = createDefaultValueCheck(value, (<BinaryExpression>target).right, sourceMapNode);
target = (<BinaryExpression>target).left;
}
if (target.kind === SyntaxKind.ObjectLiteralExpression) {
emitObjectLiteralAssignment(<ObjectLiteralExpression>target, value);
emitObjectLiteralAssignment(<ObjectLiteralExpression>target, value, sourceMapNode);
}
else if (target.kind === SyntaxKind.ArrayLiteralExpression) {
emitArrayLiteralAssignment(<ArrayLiteralExpression>target, value);
emitArrayLiteralAssignment(<ArrayLiteralExpression>target, value, sourceMapNode);
}
else {
emitAssignment(<Identifier>target, value, /*shouldEmitCommaBeforeAssignment*/ emitCount > 0);
emitAssignment(<Identifier>target, value, /*shouldEmitCommaBeforeAssignment*/ emitCount > 0, sourceMapNode);
emitCount++;
}
}
@@ -3933,14 +3947,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
emit(value);
}
else if (isAssignmentExpressionStatement) {
emitDestructuringAssignment(target, value);
// Source map node for root.left = root.right is root
// but if root is synthetic, which could be in below case, use the target which is { a }
// for ({a} of {a: string}) {
// }
emitDestructuringAssignment(target, value, nodeIsSynthesized(root) ? target : root);
}
else {
if (root.parent.kind !== SyntaxKind.ParenthesizedExpression) {
write("(");
}
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true);
emitDestructuringAssignment(target, value);
// Temporary assignment needed to emit root should highlight whole binary expression
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, root);
// Source map node for root.left = root.right is root
emitDestructuringAssignment(target, value, root);
write(", ");
emit(value);
if (root.parent.kind !== SyntaxKind.ParenthesizedExpression) {
@@ -3950,9 +3970,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
}
function emitBindingElement(target: BindingElement | VariableDeclaration, value: Expression) {
// Any temporary assignments needed to emit target = value should point to target
if (target.initializer) {
// Combine value and initializer
value = value ? createDefaultValueCheck(value, target.initializer) : target.initializer;
value = value ? createDefaultValueCheck(value, target.initializer, target) : target.initializer;
}
else if (!value) {
// Use 'void 0' in absence of value and initializer
@@ -3968,7 +3989,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
// to ensure value is evaluated exactly once. Additionally, if we have zero elements
// we need to emit *something* to ensure that in case a 'var' keyword was already emitted,
// so in that case, we'll intentionally create that temporary.
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ numElements !== 0);
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ numElements !== 0, target);
}
for (let i = 0; i < numElements; i++) {
@@ -3990,7 +4011,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
}
}
else {
emitAssignment(<Identifier>target.name, value, /*shouldEmitCommaBeforeAssignment*/ emitCount > 0);
emitAssignment(<Identifier>target.name, value, /*shouldEmitCommaBeforeAssignment*/ emitCount > 0, target);
emitCount++;
}
}
@@ -7434,6 +7455,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
}
}
function changeSourceMapEmit(writer: SourceMapWriter) {
sourceMap = writer;
emitStart = writer.emitStart;
emitEnd = writer.emitEnd;
emitPos = writer.emitPos;
setSourceFile = writer.setSourceFile;
}
function withTemporaryNoSourceMap(callback: () => void) {
const prevSourceMap = sourceMap;
setSourceMapWriterEmit(getNullSourceMapWriter());
callback();
setSourceMapWriterEmit(prevSourceMap);
}
function isSpecializedCommentHandling(node: Node): boolean {
switch (node.kind) {
// All of these entities are emitted in a specialized fashion. As such, we allow

View File

@@ -7,7 +7,8 @@ namespace ts {
setSourceFile(sourceFile: SourceFile): void;
emitPos(pos: number): void;
emitStart(range: TextRange): void;
emitEnd(range: TextRange): void;
emitEnd(range: TextRange, stopOverridingSpan?: boolean): void;
changeEmitSourcePos(): void;
getText(): string;
getSourceMappingURL(): string;
initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void;
@@ -22,8 +23,9 @@ namespace ts {
getSourceMapData(): SourceMapData { return undefined; },
setSourceFile(sourceFile: SourceFile): void { },
emitStart(range: TextRange): void { },
emitEnd(range: TextRange): void { },
emitEnd(range: TextRange, stopOverridingSpan?: boolean): void { },
emitPos(pos: number): void { },
changeEmitSourcePos(): void { },
getText(): string { return undefined; },
getSourceMappingURL(): string { return undefined; },
initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void { },
@@ -38,6 +40,8 @@ namespace ts {
const compilerOptions = host.getCompilerOptions();
let currentSourceFile: SourceFile;
let sourceMapDir: string; // The directory in which sourcemap will be
let stopOverridingSpan = false;
let modifyLastSourcePos = false;
// Current source map file and its index in the sources list
let sourceMapSourceIndex: number;
@@ -56,6 +60,7 @@ namespace ts {
emitPos,
emitStart,
emitEnd,
changeEmitSourcePos,
getText,
getSourceMappingURL,
initialize,
@@ -142,6 +147,45 @@ namespace ts {
sourceMapData = undefined;
}
function updateLastEncodedAndRecordedSpans() {
if (modifyLastSourcePos) {
// Reset the source pos
modifyLastSourcePos = false;
// Change Last recorded Map with last encoded emit line and character
lastRecordedSourceMapSpan.emittedLine = lastEncodedSourceMapSpan.emittedLine;
lastRecordedSourceMapSpan.emittedColumn = lastEncodedSourceMapSpan.emittedColumn;
// Pop sourceMapDecodedMappings to remove last entry
sourceMapData.sourceMapDecodedMappings.pop();
// Change the last encoded source map
lastEncodedSourceMapSpan = sourceMapData.sourceMapDecodedMappings.length ?
sourceMapData.sourceMapDecodedMappings[sourceMapData.sourceMapDecodedMappings.length - 1] :
undefined;
// TODO: Update lastEncodedNameIndex
// Since we dont support this any more, lets not worry about it right now.
// When we start supporting nameIndex, we will get back to this
// Change the encoded source map
const sourceMapMappings = sourceMapData.sourceMapMappings;
let lenthToSet = sourceMapMappings.length - 1;
for (; lenthToSet >= 0; lenthToSet--) {
const currentChar = sourceMapMappings.charAt(lenthToSet);
if (currentChar === ",") {
// Separator for the entry found
break;
}
if (currentChar === ";" && lenthToSet !== 0 && sourceMapMappings.charAt(lenthToSet - 1) !== ";") {
// Last line separator found
break;
}
}
sourceMapData.sourceMapMappings = sourceMapMappings.substr(0, Math.max(0, lenthToSet));
}
}
// Encoding for sourcemap span
function encodeLastRecordedSourceMapSpan() {
if (!lastRecordedSourceMapSpan || lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) {
@@ -178,6 +222,7 @@ namespace ts {
// 5. Relative namePosition 0 based
if (lastRecordedSourceMapSpan.nameIndex >= 0) {
Debug.assert(false, "We do not support name index right now, Make sure to update updateLastEncodedAndRecordedSpans when we start using this");
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.nameIndex - lastEncodedNameIndex);
lastEncodedNameIndex = lastRecordedSourceMapSpan.nameIndex;
}
@@ -219,22 +264,36 @@ namespace ts {
sourceColumn: sourceLinePos.character,
sourceIndex: sourceMapSourceIndex
};
stopOverridingSpan = false;
}
else {
else if (!stopOverridingSpan) {
// Take the new pos instead since there is no change in emittedLine and column since last location
lastRecordedSourceMapSpan.sourceLine = sourceLinePos.line;
lastRecordedSourceMapSpan.sourceColumn = sourceLinePos.character;
lastRecordedSourceMapSpan.sourceIndex = sourceMapSourceIndex;
}
updateLastEncodedAndRecordedSpans();
}
function getStartPos(range: TextRange) {
const rangeHasDecorators = !!(range as Node).decorators;
return range.pos !== -1 ? skipTrivia(currentSourceFile.text, rangeHasDecorators ? (range as Node).decorators.end : range.pos) : -1;
}
function emitStart(range: TextRange) {
const rangeHasDecorators = !!(range as Node).decorators;
emitPos(range.pos !== -1 ? skipTrivia(currentSourceFile.text, rangeHasDecorators ? (range as Node).decorators.end : range.pos) : -1);
emitPos(getStartPos(range));
}
function emitEnd(range: TextRange) {
function emitEnd(range: TextRange, stopOverridingEnd?: boolean) {
emitPos(range.end);
stopOverridingSpan = stopOverridingEnd;
}
function changeEmitSourcePos() {
Debug.assert(!modifyLastSourcePos);
modifyLastSourcePos = true;
}
function setSourceFile(sourceFile: SourceFile) {

View File

@@ -1080,9 +1080,15 @@ namespace FourSlash {
}
public baselineCurrentFileBreakpointLocations() {
let baselineFile = this.testData.globalOptions[metadataOptionNames.baselineFile];
if (!baselineFile) {
baselineFile = this.activeFile.fileName.replace(this.basePath + "/breakpointValidation", "bpSpan");
baselineFile = baselineFile.replace(".ts", ".baseline");
}
Harness.Baseline.runBaseline(
"Breakpoint Locations for " + this.activeFile.fileName,
this.testData.globalOptions[metadataOptionNames.baselineFile],
baselineFile,
() => {
return this.baselineCurrentFileLocations(pos => this.getBreakpointStatementLocation(pos));
},

View File

@@ -45,6 +45,10 @@ namespace ts.BreakpointResolver {
return createTextSpanFromBounds(start, (endNode || startNode).getEnd());
}
function textSpanEndingAtNextToken(startNode: Node, previousTokenToFindNextEndToken: Node): TextSpan {
return textSpan(startNode, findNextToken(previousTokenToFindNextEndToken, previousTokenToFindNextEndToken.parent));
}
function spanInNodeIfStartsOnSameLine(node: Node, otherwiseOnNode?: Node): TextSpan {
if (node && lineOfPosition === sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line) {
return spanInNode(node);
@@ -66,33 +70,6 @@ namespace ts.BreakpointResolver {
function spanInNode(node: Node): TextSpan {
if (node) {
if (isExpression(node)) {
if (node.parent.kind === SyntaxKind.DoStatement) {
// Set span as if on while keyword
return spanInPreviousNode(node);
}
if (node.parent.kind === SyntaxKind.Decorator) {
// Set breakpoint on the decorator emit
return spanInNode(node.parent);
}
if (node.parent.kind === SyntaxKind.ForStatement) {
// For now lets set the span on this expression, fix it later
return textSpan(node);
}
if (node.parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node.parent).operatorToken.kind === SyntaxKind.CommaToken) {
// if this is comma expression, the breakpoint is possible in this expression
return textSpan(node);
}
if (node.parent.kind === SyntaxKind.ArrowFunction && (<FunctionLikeDeclaration>node.parent).body === node) {
// If this is body of arrow function, it is allowed to have the breakpoint
return textSpan(node);
}
}
switch (node.kind) {
case SyntaxKind.VariableStatement:
// Span on first variable declaration
@@ -120,7 +97,7 @@ namespace ts.BreakpointResolver {
if (isFunctionBlock(node)) {
return spanInFunctionBlock(<Block>node);
}
// Fall through
// Fall through
case SyntaxKind.ModuleBlock:
return spanInBlock(<Block>node);
@@ -137,7 +114,7 @@ namespace ts.BreakpointResolver {
case SyntaxKind.WhileStatement:
// Span on while(...)
return textSpan(node, findNextToken((<WhileStatement>node).expression, node));
return textSpanEndingAtNextToken(node, (<WhileStatement>node).expression);
case SyntaxKind.DoStatement:
// span in statement of the do statement
@@ -149,7 +126,7 @@ namespace ts.BreakpointResolver {
case SyntaxKind.IfStatement:
// set on if(..) span
return textSpan(node, findNextToken((<IfStatement>node).expression, node));
return textSpanEndingAtNextToken(node, (<IfStatement>node).expression);
case SyntaxKind.LabeledStatement:
// span in statement
@@ -164,13 +141,16 @@ namespace ts.BreakpointResolver {
return spanInForStatement(<ForStatement>node);
case SyntaxKind.ForInStatement:
// span of for (a in ...)
return textSpanEndingAtNextToken(node, (<ForInStatement>node).expression);
case SyntaxKind.ForOfStatement:
// span on for (a in ...)
return textSpan(node, findNextToken((<ForInStatement | ForOfStatement>node).expression, node));
// span in initializer
return spanInInitializerOfForLike(<ForOfStatement | ForInStatement>node);
case SyntaxKind.SwitchStatement:
// span on switch(...)
return textSpan(node, findNextToken((<SwitchStatement>node).expression, node));
return textSpanEndingAtNextToken(node, (<SwitchStatement>node).expression);
case SyntaxKind.CaseClause:
case SyntaxKind.DefaultClause:
@@ -210,8 +190,7 @@ namespace ts.BreakpointResolver {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.EnumMember:
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
case SyntaxKind.BindingElement:
// span on complete node
return textSpan(node);
@@ -222,6 +201,10 @@ namespace ts.BreakpointResolver {
case SyntaxKind.Decorator:
return spanInNodeArray(node.parent.decorators);
case SyntaxKind.ObjectBindingPattern:
case SyntaxKind.ArrayBindingPattern:
return spanInBindingPattern(<BindingPattern>node);
// No breakpoint in interface, type alias
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
@@ -234,14 +217,17 @@ namespace ts.BreakpointResolver {
case SyntaxKind.CommaToken:
return spanInPreviousNode(node)
case SyntaxKind.OpenBraceToken:
return spanInOpenBraceToken(node);
case SyntaxKind.CloseBraceToken:
return spanInCloseBraceToken(node);
case SyntaxKind.OpenParenToken:
case SyntaxKind.CloseBracketToken:
return spanInCloseBracketToken(node);
case SyntaxKind.OpenParenToken:
return spanInOpenParenToken(node);
case SyntaxKind.CloseParenToken:
@@ -263,15 +249,93 @@ namespace ts.BreakpointResolver {
case SyntaxKind.FinallyKeyword:
return spanInNextNode(node);
case SyntaxKind.OfKeyword:
return spanInOfKeyword(node);
default:
// If this is name of property assignment, set breakpoint in the initializer
if (node.parent.kind === SyntaxKind.PropertyAssignment && (<PropertyDeclaration>node.parent).name === node) {
return spanInNode((<PropertyDeclaration>node.parent).initializer);
// Destructuring pattern in destructuring assignment
// [a, b, c] of
// [a, b, c] = expression
if (isArrayLiteralOrObjectLiteralDestructuringPattern(node)) {
return spanInArrayLiteralOrObjectLiteralDestructuringPattern(<DestructuringPattern>node);
}
// Set breakpoint on identifier element of destructuring pattern
// a or ...c or d: x from
// [a, b, ...c] or { a, b } or { d: x } from destructuring pattern
if ((node.kind === SyntaxKind.Identifier ||
node.kind == SyntaxKind.SpreadElementExpression ||
node.kind === SyntaxKind.PropertyAssignment ||
node.kind === SyntaxKind.ShorthandPropertyAssignment) &&
isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent)) {
return textSpan(node);
}
if (node.kind === SyntaxKind.BinaryExpression) {
const binaryExpression = <BinaryExpression>node;
// Set breakpoint in destructuring pattern if its destructuring assignment
// [a, b, c] or {a, b, c} of
// [a, b, c] = expression or
// {a, b, c} = expression
if (isArrayLiteralOrObjectLiteralDestructuringPattern(binaryExpression.left)) {
return spanInArrayLiteralOrObjectLiteralDestructuringPattern(
<ArrayLiteralExpression | ObjectLiteralExpression>binaryExpression.left);
}
if (binaryExpression.operatorToken.kind === SyntaxKind.EqualsToken &&
isArrayLiteralOrObjectLiteralDestructuringPattern(binaryExpression.parent)) {
// Set breakpoint on assignment expression element of destructuring pattern
// a = expression of
// [a = expression, b, c] = someExpression or
// { a = expression, b, c } = someExpression
return textSpan(node);
}
if (binaryExpression.operatorToken.kind === SyntaxKind.CommaToken) {
return spanInNode(binaryExpression.left);
}
}
if (isExpression(node)) {
switch (node.parent.kind) {
case SyntaxKind.DoStatement:
// Set span as if on while keyword
return spanInPreviousNode(node);
case SyntaxKind.Decorator:
// Set breakpoint on the decorator emit
return spanInNode(node.parent);
case SyntaxKind.ForStatement:
case SyntaxKind.ForOfStatement:
return textSpan(node);
case SyntaxKind.BinaryExpression:
if ((<BinaryExpression>node.parent).operatorToken.kind === SyntaxKind.CommaToken) {
// if this is comma expression, the breakpoint is possible in this expression
return textSpan(node);
}
break;
case SyntaxKind.ArrowFunction:
if ((<FunctionLikeDeclaration>node.parent).body === node) {
// If this is body of arrow function, it is allowed to have the breakpoint
return textSpan(node);
}
break;
}
}
// If this is name of property assignment, set breakpoint in the initializer
if (node.parent.kind === SyntaxKind.PropertyAssignment &&
(<PropertyDeclaration>node.parent).name === node &&
!isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.parent)) {
return spanInNode((<PropertyDeclaration>node.parent).initializer);
}
// Breakpoint in type assertion goes to its operand
if (node.parent.kind === SyntaxKind.TypeAssertionExpression && (<TypeAssertion>node.parent).type === node) {
return spanInNode((<TypeAssertion>node.parent).expression);
return spanInNextNode((<TypeAssertion>node.parent).type);
}
// return type of function go to previous token
@@ -279,48 +343,70 @@ namespace ts.BreakpointResolver {
return spanInPreviousNode(node);
}
// initializer of variable/parameter declaration go to previous node
if ((node.parent.kind === SyntaxKind.VariableDeclaration ||
node.parent.kind === SyntaxKind.Parameter)) {
const paramOrVarDecl = <VariableDeclaration | ParameterDeclaration>node.parent;
if (paramOrVarDecl.initializer === node ||
paramOrVarDecl.type === node ||
isAssignmentOperator(node.kind)) {
return spanInPreviousNode(node);
}
}
if (node.parent.kind === SyntaxKind.BinaryExpression) {
const binaryExpression = <BinaryExpression>node.parent;
if (isArrayLiteralOrObjectLiteralDestructuringPattern(binaryExpression.left) &&
(binaryExpression.right === node ||
binaryExpression.operatorToken === node)) {
// If initializer of destructuring assignment move to previous token
return spanInPreviousNode(node);
}
}
// Default go to parent to set the breakpoint
return spanInNode(node.parent);
}
}
function textSpanFromVariableDeclaration(variableDeclaration: VariableDeclaration): TextSpan {
let declarations = variableDeclaration.parent.declarations;
if (declarations && declarations[0] === variableDeclaration) {
// First declaration - include let keyword
return textSpan(findPrecedingToken(variableDeclaration.pos, sourceFile, variableDeclaration.parent), variableDeclaration);
}
else {
// Span only on this declaration
return textSpan(variableDeclaration);
}
}
function spanInVariableDeclaration(variableDeclaration: VariableDeclaration): TextSpan {
// If declaration of for in statement, just set the span in parent
if (variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement ||
variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
if (variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) {
return spanInNode(variableDeclaration.parent.parent);
}
let isParentVariableStatement = variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement;
let isDeclarationOfForStatement = variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement && contains((<VariableDeclarationList>(<ForStatement>variableDeclaration.parent.parent).initializer).declarations, variableDeclaration);
let declarations = isParentVariableStatement
? (<VariableStatement>variableDeclaration.parent.parent).declarationList.declarations
: isDeclarationOfForStatement
? (<VariableDeclarationList>(<ForStatement>variableDeclaration.parent.parent).initializer).declarations
: undefined;
// If this is a destructuring pattern set breakpoint in binding pattern
if (isBindingPattern(variableDeclaration.name)) {
return spanInBindingPattern(<BindingPattern>variableDeclaration.name);
}
// Breakpoint is possible in variableDeclaration only if there is initialization
if (variableDeclaration.initializer || (variableDeclaration.flags & NodeFlags.Export)) {
if (declarations && declarations[0] === variableDeclaration) {
if (isParentVariableStatement) {
// First declaration - include let keyword
return textSpan(variableDeclaration.parent, variableDeclaration);
}
else {
Debug.assert(isDeclarationOfForStatement);
// Include let keyword from for statement declarations in the span
return textSpan(findPrecedingToken(variableDeclaration.pos, sourceFile, variableDeclaration.parent), variableDeclaration);
}
}
else {
// Span only on this declaration
return textSpan(variableDeclaration);
}
// or its declaration from 'for of'
if (variableDeclaration.initializer ||
(variableDeclaration.flags & NodeFlags.Export) ||
variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
return textSpanFromVariableDeclaration(variableDeclaration);
}
else if (declarations && declarations[0] !== variableDeclaration) {
let declarations = variableDeclaration.parent.declarations;
if (declarations && declarations[0] !== variableDeclaration) {
// If we cant set breakpoint on this declaration, set it on previous one
let indexOfCurrentDeclaration = indexOf(declarations, variableDeclaration);
return spanInVariableDeclaration(declarations[indexOfCurrentDeclaration - 1]);
// Because the variable declaration may be binding pattern and
// we would like to set breakpoint in last binding element if thats the case,
// use preceding token instead
return spanInNode(findPrecedingToken(variableDeclaration.pos, sourceFile, variableDeclaration.parent));
}
}
@@ -331,7 +417,11 @@ namespace ts.BreakpointResolver {
}
function spanInParameterDeclaration(parameter: ParameterDeclaration): TextSpan {
if (canHaveSpanInParameterDeclaration(parameter)) {
if (isBindingPattern(parameter.name)) {
// set breakpoint in binding pattern
return spanInBindingPattern(<BindingPattern>parameter.name);
}
else if (canHaveSpanInParameterDeclaration(parameter)) {
return textSpan(parameter);
}
else {
@@ -388,11 +478,11 @@ namespace ts.BreakpointResolver {
case SyntaxKind.WhileStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return spanInNodeIfStartsOnSameLine(block.parent, block.statements[0]);
// Set span on previous token if it starts on same line otherwise on the first statement of the block
case SyntaxKind.ForStatement:
case SyntaxKind.ForOfStatement:
return spanInNodeIfStartsOnSameLine(findPrecedingToken(block.pos, sourceFile, block.parent), block.statements[0]);
}
@@ -400,17 +490,23 @@ namespace ts.BreakpointResolver {
return spanInNode(block.statements[0]);
}
function spanInInitializerOfForLike(forLikeStaement: ForStatement | ForOfStatement | ForInStatement): TextSpan {
if (forLikeStaement.initializer.kind === SyntaxKind.VariableDeclarationList) {
// declaration list, set breakpoint in first declaration
let variableDeclarationList = <VariableDeclarationList>forLikeStaement.initializer;
if (variableDeclarationList.declarations.length > 0) {
return spanInNode(variableDeclarationList.declarations[0]);
}
}
else {
// Expression - set breakpoint in it
return spanInNode(forLikeStaement.initializer);
}
}
function spanInForStatement(forStatement: ForStatement): TextSpan {
if (forStatement.initializer) {
if (forStatement.initializer.kind === SyntaxKind.VariableDeclarationList) {
let variableDeclarationList = <VariableDeclarationList>forStatement.initializer;
if (variableDeclarationList.declarations.length > 0) {
return spanInNode(variableDeclarationList.declarations[0]);
}
}
else {
return spanInNode(forStatement.initializer);
}
return spanInInitializerOfForLike(forStatement);
}
if (forStatement.condition) {
@@ -421,6 +517,45 @@ namespace ts.BreakpointResolver {
}
}
function spanInBindingPattern(bindingPattern: BindingPattern): TextSpan {
// Set breakpoint in first binding element
let firstBindingElement = forEach(bindingPattern.elements,
element => element.kind !== SyntaxKind.OmittedExpression ? element : undefined);
if (firstBindingElement) {
return spanInNode(firstBindingElement);
}
// Empty binding pattern of binding element, set breakpoint on binding element
if (bindingPattern.parent.kind === SyntaxKind.BindingElement) {
return textSpan(bindingPattern.parent);
}
// Variable declaration is used as the span
return textSpanFromVariableDeclaration(<VariableDeclaration>bindingPattern.parent);
}
function spanInArrayLiteralOrObjectLiteralDestructuringPattern(node: DestructuringPattern): TextSpan {
Debug.assert(node.kind !== SyntaxKind.ArrayBindingPattern && node.kind !== SyntaxKind.ObjectBindingPattern);
const elements: NodeArray<Expression | ObjectLiteralElement> =
node.kind === SyntaxKind.ArrayLiteralExpression ?
(<ArrayLiteralExpression>node).elements :
(<ObjectLiteralExpression>node).properties;
const firstBindingElement = forEach(elements,
element => element.kind !== SyntaxKind.OmittedExpression ? element : undefined);
if (firstBindingElement) {
return spanInNode(firstBindingElement);
}
// Could be ArrayLiteral from destructuring assignment or
// just nested element in another destructuring assignment
// set breakpoint on assignment when parent is destructuring assignment
// Otherwise set breakpoint for this element
return textSpan(node.parent.kind === SyntaxKind.BinaryExpression ? node.parent : node);
}
// Tokens:
function spanInOpenBraceToken(node: Node): TextSpan {
switch (node.parent.kind) {
@@ -472,18 +607,52 @@ namespace ts.BreakpointResolver {
}
return undefined;
case SyntaxKind.ObjectBindingPattern:
// Breakpoint in last binding element or binding pattern if it contains no elements
let bindingPattern = <BindingPattern>node.parent;
return spanInNode(lastOrUndefined(bindingPattern.elements) || bindingPattern);
// Default to parent node
default:
if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent)) {
// Breakpoint in last binding element or binding pattern if it contains no elements
let objectLiteral = <ObjectLiteralExpression>node.parent;
return textSpan(lastOrUndefined(objectLiteral.properties) || objectLiteral);
}
return spanInNode(node.parent);
}
}
function spanInCloseBracketToken(node: Node): TextSpan {
switch (node.parent.kind) {
case SyntaxKind.ArrayBindingPattern:
// Breakpoint in last binding element or binding pattern if it contains no elements
let bindingPattern = <BindingPattern>node.parent;
return textSpan(lastOrUndefined(bindingPattern.elements) || bindingPattern);
default:
if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent)) {
// Breakpoint in last binding element or binding pattern if it contains no elements
let arrayLiteral = <ArrayLiteralExpression>node.parent;
return textSpan(lastOrUndefined(arrayLiteral.elements) || arrayLiteral);
}
// Default to parent node
return spanInNode(node.parent);
}
}
function spanInOpenParenToken(node: Node): TextSpan {
if (node.parent.kind === SyntaxKind.DoStatement) {
// Go to while keyword and do action instead
if (node.parent.kind === SyntaxKind.DoStatement || // Go to while keyword and do action instead
node.parent.kind === SyntaxKind.CallExpression ||
node.parent.kind === SyntaxKind.NewExpression) {
return spanInPreviousNode(node);
}
if (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
return spanInNextNode(node);
}
// Default to parent node
return spanInNode(node.parent);
}
@@ -502,6 +671,10 @@ namespace ts.BreakpointResolver {
case SyntaxKind.WhileStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.ForStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
case SyntaxKind.ParenthesizedExpression:
return spanInPreviousNode(node);
// Default to parent node
@@ -512,7 +685,9 @@ namespace ts.BreakpointResolver {
function spanInColonToken(node: Node): TextSpan {
// Is this : specifying return annotation of the function declaration
if (isFunctionLike(node.parent) || node.parent.kind === SyntaxKind.PropertyAssignment) {
if (isFunctionLike(node.parent) ||
node.parent.kind === SyntaxKind.PropertyAssignment ||
node.parent.kind === SyntaxKind.Parameter) {
return spanInPreviousNode(node);
}
@@ -521,7 +696,7 @@ namespace ts.BreakpointResolver {
function spanInGreaterThanOrLessThanToken(node: Node): TextSpan {
if (node.parent.kind === SyntaxKind.TypeAssertionExpression) {
return spanInNode((<TypeAssertion>node.parent).expression);
return spanInNextNode(node);
}
return spanInNode(node.parent);
@@ -530,7 +705,17 @@ namespace ts.BreakpointResolver {
function spanInWhileKeyword(node: Node): TextSpan {
if (node.parent.kind === SyntaxKind.DoStatement) {
// Set span on while expression
return textSpan(node, findNextToken((<DoStatement>node.parent).expression, node.parent));
return textSpanEndingAtNextToken(node, (<DoStatement>node.parent).expression);
}
// Default to parent node
return spanInNode(node.parent);
}
function spanInOfKeyword(node: Node): TextSpan {
if (node.parent.kind === SyntaxKind.ForOfStatement) {
// set using next token
return spanInNextNode(node);
}
// Default to parent node

View File

@@ -608,6 +608,36 @@ namespace ts {
}
return true;
}
export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) {
if (node.kind === SyntaxKind.ArrayLiteralExpression ||
node.kind === SyntaxKind.ObjectLiteralExpression) {
// [a,b,c] from:
// [a, b, c] = someExpression;
if (node.parent.kind === SyntaxKind.BinaryExpression &&
(<BinaryExpression>node.parent).left === node &&
(<BinaryExpression>node.parent).operatorToken.kind === SyntaxKind.EqualsToken) {
return true;
}
// [a, b, c] from:
// for([a, b, c] of expression)
if (node.parent.kind === SyntaxKind.ForOfStatement &&
(<ForOfStatement>node.parent).initializer === node) {
return true;
}
// [a, b, c] of
// [x, [a, b, c] ] = someExpression
// or
// {x, a: {a, b, c} } = someExpression
if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) {
return true;
}
}
return false;
}
}
// Display-part writer helpers