Addesses a number of comment/sourcemap related issues

This commit is contained in:
Ron Buckton
2016-04-15 18:39:30 -07:00
parent d2a7288579
commit 6225a5aa40
7 changed files with 211 additions and 61 deletions
+27 -5
View File
@@ -13,7 +13,10 @@ namespace ts {
getTrailingCommentsOfPosition(pos: number): CommentRange[];
emitLeadingComments(range: TextRange, comments: CommentRange[]): void;
emitTrailingComments(range: TextRange, comments: CommentRange[]): void;
emitDetachedComments(range: TextRange): void;
emitLeadingDetachedComments(range: TextRange): void;
emitLeadingDetachedComments(range: TextRange, contextNode: Node, shouldSkipCommentsForNodeCallback: (node: Node) => boolean): void;
emitTrailingDetachedComments(range: TextRange): void;
emitTrailingDetachedComments(range: TextRange, contextNode: Node, shouldSkipCommentsForNodeCallback: (node: Node) => boolean): void;
}
export function createCommentWriter(host: EmitHost, writer: EmitTextWriter, sourceMap: SourceMapWriter): CommentWriter {
@@ -46,10 +49,15 @@ namespace ts {
getTrailingCommentsOfPosition(pos: number): CommentRange[] { return undefined; },
emitLeadingComments(range: TextRange, comments: CommentRange[]): void { },
emitTrailingComments(range: TextRange, comments: CommentRange[]): void { },
emitDetachedComments,
emitLeadingDetachedComments,
emitTrailingDetachedComments(node: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void {}
};
function emitDetachedComments(node: TextRange): void {
function emitLeadingDetachedComments(node: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void {
if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) {
return;
}
emitDetachedCommentsAndUpdateCommentsInfo(node, /*removeComments*/ true);
}
}
@@ -65,7 +73,8 @@ namespace ts {
getTrailingCommentsOfPosition,
emitLeadingComments,
emitTrailingComments,
emitDetachedComments,
emitLeadingDetachedComments,
emitTrailingDetachedComments
};
function getLeadingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[];
@@ -154,10 +163,23 @@ namespace ts {
emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment);
}
function emitDetachedComments(range: TextRange) {
function emitLeadingDetachedComments(range: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void {
if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) {
return;
}
emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false);
}
function emitTrailingDetachedComments(range: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void {
if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) {
return;
}
range = collapseRangeToEnd(range);
emitLeadingComments(range, getLeadingComments(range));
}
function hasConsumedCommentRange(comment: CommentRange) {
return comment.end === consumedCommentRanges[comment.pos];
}
+5 -4
View File
@@ -651,13 +651,14 @@ namespace ts {
/**
* Creates a synthetic expression to act as a placeholder for a not-emitted expression in
* order to preserve comments.
* order to preserve comments or sourcemap positions.
*
* @param expression The inner expression to emit.
* @param original The original outer expression.
* @param location The location for the expression. Defaults to the positions from "original" if provided.
*/
export function createPartiallyEmittedExpression(expression: Expression, original: Node) {
const node = <PartiallyEmittedExpression>createNode(SyntaxKind.PartiallyEmittedExpression, /*location*/ original);
export function createPartiallyEmittedExpression(expression: Expression, original?: Node, location?: TextRange) {
const node = <PartiallyEmittedExpression>createNode(SyntaxKind.PartiallyEmittedExpression, /*location*/ location || original);
node.expression = expression;
node.original = original;
return node;
@@ -1257,7 +1258,7 @@ namespace ts {
* The function needs to be called during each transformation step.
* This function needs to be called whenever we transform the statement
* list of a source file, namespace, or function-like body.
*
*
* @param target: result statements array
* @param source: origin statements array
* @param ensureUseStrict: boolean determining whether the function need to add prologue-directives
+42 -20
View File
@@ -140,7 +140,8 @@ const _super = (function (geti, seti) {
getTrailingCommentsOfPosition,
emitLeadingComments,
emitTrailingComments,
emitDetachedComments
emitLeadingDetachedComments,
emitTrailingDetachedComments
} = comments;
let context: TransformationContext;
@@ -298,24 +299,45 @@ const _super = (function (geti, seti) {
const leadingComments = getLeadingComments(node, shouldSkipCommentsForNode);
const trailingComments = getTrailingComments(node, shouldSkipCommentsForNode);
emitLeadingComments(node, leadingComments);
emitStart(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren);
emitStart(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren);
emitWorker(node);
emitEnd(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren);
emitEnd(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren);
emitTrailingComments(node, trailingComments);
}
}
/**
* Determines whether to skip comment emit for a node.
*
* We do not emit comments for NotEmittedStatement nodes or any node that has
* NodeEmitFlags.NoComments.
*
* @param node A Node.
*/
function shouldSkipCommentsForNode(node: Node) {
return isNotEmittedStatement(node)
|| (getNodeEmitFlags(node) & NodeEmitFlags.NoComments) !== 0;
}
function shouldIgnoreSourceMapForNode(node: Node) {
return isNotEmittedOrPartiallyEmittedNode(node)
/**
* Determines whether to skip source map emit for a node.
*
* We do not emit source maps for NotEmittedStatement nodes or any node that
* has NodeEmitFlags.NoSourceMap.
*
* @param node A Node.
*/
function shouldSkipSourceMapForNode(node: Node) {
return isNotEmittedStatement(node)
|| (getNodeEmitFlags(node) & NodeEmitFlags.NoSourceMap) !== 0;
}
function shouldIgnoreSourceMapForChildren(node: Node) {
/**
* Determines whether to skip source map emit for a node and its children.
*
* We do not emit source maps for a node that has NodeEmitFlags.NoNestedSourceMaps.
*/
function shouldSkipSourceMapForChildren(node: Node) {
return (getNodeEmitFlags(node) & NodeEmitFlags.NoNestedSourceMaps) !== 0;
}
@@ -1064,7 +1086,6 @@ const _super = (function (geti, seti) {
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
emitSignatureAndBody(node, emitArrowFunctionHead);
}
function emitArrowFunctionHead(node: ArrowFunction) {
@@ -1375,7 +1396,8 @@ const _super = (function (geti, seti) {
}
function emitDebuggerStatement(node: DebuggerStatement) {
write("debugger;");
writeToken(SyntaxKind.DebuggerKeyword, node.pos);
write(";");
}
//
@@ -1417,9 +1439,7 @@ const _super = (function (geti, seti) {
tempFlags = 0;
startLexicalEnvironment();
emitSignatureHead(node);
write(" {");
emitBlockFunctionBody(node, body);
writeToken(SyntaxKind.CloseBraceToken, node.end)
emitBlockFunctionBodyAndEndLexicalEnvironment(node, body);
if (indentedFlag) {
decreaseIndent();
}
@@ -1483,10 +1503,12 @@ const _super = (function (geti, seti) {
return true;
}
function emitBlockFunctionBody(parentNode: Node, body: Block) {
function emitBlockFunctionBodyAndEndLexicalEnvironment(parentNode: Node, body: Block) {
write(" {");
const startingLine = writer.getLine();
increaseIndent();
emitDetachedComments(body.statements);
emitLeadingDetachedComments(body.statements, body, shouldSkipCommentsForNode);
// Emit all the prologue directives (like "use strict").
const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true);
@@ -1503,10 +1525,9 @@ const _super = (function (geti, seti) {
const endingLine = writer.getLine();
emitLexicalEnvironment(endLexicalEnvironment(), /*newLine*/ startingLine !== endingLine);
const range = collapseRangeToEnd(body.statements);
emitLeadingComments(range, getLeadingComments(range));
emitTrailingDetachedComments(body.statements, body, shouldSkipCommentsForNode);
decreaseIndent();
writeToken(SyntaxKind.CloseBraceToken, body.statements.end);
}
function emitClassDeclaration(node: ClassDeclaration) {
@@ -1882,7 +1903,7 @@ const _super = (function (geti, seti) {
function emitSourceFile(node: SourceFile) {
writeLine();
emitShebang();
emitDetachedComments(node);
emitLeadingDetachedComments(node);
const statements = node.statements;
const statementOffset = emitPrologueDirectives(statements);
@@ -1900,7 +1921,7 @@ const _super = (function (geti, seti) {
tempFlags = savedTempFlags;
}
emitLeadingComments(node.endOfFileToken, getLeadingComments(node.endOfFileToken));
emitTrailingDetachedComments(node.statements);
}
// Transformation nodes
@@ -2281,6 +2302,7 @@ const _super = (function (geti, seti) {
}
function writeToken(token: SyntaxKind, tokenStartPos: number) {
tokenStartPos = skipTrivia(currentText, tokenStartPos);
emitPos(tokenStartPos);
const tokenEndPos = writeTokenText(token, tokenStartPos);
emitPos(tokenEndPos);
@@ -2295,9 +2317,9 @@ const _super = (function (geti, seti) {
function writeTokenNode(node: Node) {
if (node) {
emitStart(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren);
emitStart(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren);
writeTokenText(node.kind);
emitEnd(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren);
emitEnd(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren);
}
}
+79 -23
View File
@@ -156,6 +156,7 @@ namespace ts {
context.expressionSubstitution = substituteExpression;
let currentSourceFile: SourceFile;
let currentText: string;
let currentParent: Node;
let currentNode: Node;
let enclosingBlockScopeContainer: Node;
@@ -183,6 +184,7 @@ namespace ts {
function transformSourceFile(node: SourceFile) {
currentSourceFile = node;
currentText = node.text;
return visitNode(node, visitor, isSourceFile);
}
@@ -609,14 +611,14 @@ namespace ts {
if (node.name) {
enableSubstitutionsForBlockScopedBindings();
}
const closingBraceLocation = { pos: node.end - 1, end: node.end };
const baseTypeNode = getClassExtendsHeritageClauseElement(node);
const classFunction = createFunctionExpression(
/*asteriskToken*/ undefined,
/*name*/ undefined,
baseTypeNode ? [createParameter("_super")] : [],
transformClassBody(node, baseTypeNode !== undefined, closingBraceLocation),
closingBraceLocation
transformClassBody(node, baseTypeNode !== undefined)
);
// To preserve the behavior of the old emitter, we explicitly indent
@@ -626,15 +628,23 @@ namespace ts {
setNodeEmitFlags(classFunction, NodeEmitFlags.Indented);
}
// "inner" and "outer" below are added purely to preserve source map locations from
// the old emitter
const inner = createPartiallyEmittedExpression(classFunction);
inner.end = node.end;
setNodeEmitFlags(inner, NodeEmitFlags.NoComments);
const outer = createPartiallyEmittedExpression(inner);
outer.end = node.pos;
setNodeEmitFlags(outer, NodeEmitFlags.NoComments);
return createParen(
createCall(
classFunction,
outer,
baseTypeNode
? [visitNode(baseTypeNode.expression, visitor, isExpression)]
: [],
closingBraceLocation
),
closingBraceLocation
: []
)
);
}
@@ -644,15 +654,33 @@ namespace ts {
* @param node A ClassExpression or ClassDeclaration node.
* @param hasExtendsClause A value indicating whether the class has an `extends` clause.
*/
function transformClassBody(node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean, closingBraceLocation: TextRange): Block {
function transformClassBody(node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): Block {
const statements: Statement[] = [];
startLexicalEnvironment();
addExtendsHelperIfNeeded(statements, node, hasExtendsClause);
addConstructor(statements, node, hasExtendsClause);
addClassMembers(statements, node);
statements.push(createReturn(getDeclarationName(node), /*location*/ closingBraceLocation));
// Create a synthetic text range for the return statement.
const closingBraceLocation = createTokenRange(skipTrivia(currentText, node.members.end), SyntaxKind.CloseBraceToken);
const name = getDeclarationName(node);
// The following partially-emitted expression exists purely to align our sourcemap
// emit with the original emitter.
const outer = createPartiallyEmittedExpression(name);
outer.end = closingBraceLocation.end;
setNodeEmitFlags(outer, NodeEmitFlags.NoComments);
const statement = createReturn(outer);
statement.pos = closingBraceLocation.pos;
statements.push(statement);
setNodeEmitFlags(statement, NodeEmitFlags.NoComments);
addRange(statements, endLexicalEnvironment());
return createBlock(statements, /*location*/ undefined, /*multiLine*/ true);
const block = createBlock(createNodeArray(statements, /*location*/ node.members), /*location*/ undefined, /*multiLine*/ true);
setNodeEmitFlags(block, NodeEmitFlags.NoComments);
return block;
}
/**
@@ -688,7 +716,7 @@ namespace ts {
/*asteriskToken*/ undefined,
getDeclarationName(node),
transformConstructorParameters(constructor, hasSynthesizedSuper),
transformConstructorBody(constructor, hasExtendsClause, hasSynthesizedSuper),
transformConstructorBody(constructor, node, hasExtendsClause, hasSynthesizedSuper),
/*location*/ constructor || node
)
);
@@ -718,11 +746,12 @@ namespace ts {
* Transforms the body of a constructor declaration of a class.
*
* @param constructor The constructor for the class.
* @param node The node which contains the constructor.
* @param hasExtendsClause A value indicating whether the class has an `extends` clause.
* @param hasSynthesizedSuper A value indicating whether the constructor starts with a
* synthesized `super` call.
*/
function transformConstructorBody(constructor: ConstructorDeclaration, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) {
function transformConstructorBody(constructor: ConstructorDeclaration, node: ClassDeclaration | ClassExpression, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) {
const statements: Statement[] = [];
startLexicalEnvironment();
if (constructor) {
@@ -739,14 +768,20 @@ namespace ts {
}
addRange(statements, endLexicalEnvironment());
return createBlock(
const block = createBlock(
createNodeArray(
statements,
/*location*/ constructor ? constructor.body.statements : undefined
/*location*/ constructor ? constructor.body.statements : node.members
),
/*location*/ constructor ? constructor.body : undefined,
/*location*/ constructor ? constructor.body : node,
/*multiLine*/ true
);
if (!constructor) {
setNodeEmitFlags(block, NodeEmitFlags.NoComments);
}
return block;
}
function transformConstructorBodyWithSynthesizedSuper(node: ConstructorDeclaration) {
@@ -1077,16 +1112,24 @@ namespace ts {
* @param member The MethodDeclaration node.
*/
function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration) {
return createStatement(
const statement = createStatement(
createAssignment(
createMemberAccessForPropertyName(
receiver,
visitNode(member.name, visitor, isPropertyName)
visitNode(member.name, visitor, isPropertyName),
/*location*/ member.name
),
transformFunctionLikeToExpression(member, /*location*/ undefined, /*name*/ undefined)
transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined),
/*location*/ moveRangeEnd(member, -1)
),
/*location*/ member
);
// The location for the statement is used to emit comments only.
// No source map should be emitted for this statement to align with the
// old emitter.
setNodeEmitFlags(statement, NodeEmitFlags.NoSourceMap);
return statement;
}
/**
@@ -1096,9 +1139,16 @@ namespace ts {
* @param accessors The set of related get/set accessors.
*/
function transformAccessorsToStatement(receiver: LeftHandSideExpression, accessors: AllAccessorDeclarations): Statement {
return createStatement(
transformAccessorsToExpression(receiver, accessors)
const statement = createStatement(
transformAccessorsToExpression(receiver, accessors),
/*location*/ accessors.firstAccessor
);
// The location for the statement is used to emit source maps only.
// No comments should be emitted for this statement to align with the
// old emitter.
setNodeEmitFlags(statement, NodeEmitFlags.NoComments);
return statement;
}
/**
@@ -1122,7 +1172,7 @@ namespace ts {
configurable: true
},
/*preferNewLine*/ true,
/*location*/ firstAccessor,
/*location*/ undefined,
/*descriptorLocations*/ {
get: getAccessor,
set: setAccessor
@@ -1224,6 +1274,7 @@ namespace ts {
// addPrologueDirectives will simply put already-existing directives at the beginning of the target statement-array
statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false);
}
addCaptureThisForNodeIfNeeded(statements, node);
addDefaultValueAssignmentsIfNeeded(statements, node);
addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false);
@@ -1244,7 +1295,12 @@ namespace ts {
}
else {
Debug.assert(node.kind === SyntaxKind.ArrowFunction);
statementsLocation = body;
// To align with the old emitter, we use a synthetic end position on the location
// for the statement list we synthesize when we down-level an arrow function with
// an expression function body. This prevents both comments and source maps from
// being emitted for the end position only.
statementsLocation = moveRangeEnd(body, -1);
const equalsGreaterThanToken = (<ArrowFunction>node).equalsGreaterThanToken;
if (!nodeIsSynthesized(equalsGreaterThanToken) && !nodeIsSynthesized(body)) {
@@ -1258,7 +1314,7 @@ namespace ts {
const expression = visitNode(body, visitor, isExpression);
if (expression) {
statements.push(createReturn(expression));
statements.push(createReturn(expression, /*location*/ statementsLocation));
}
}
+55 -4
View File
@@ -2943,12 +2943,63 @@ namespace ts {
}
}
export function collapseRangeToStart(range: TextRange) {
return range.pos === range.end ? range : { pos: range.pos, end: range.pos };
/**
* Creates a new TextRange from a provided range with a new end position.
*
* @param range A TextRange.
* @param end The new end position.
*/
export function moveRangeEnd(range: TextRange, end: number): TextRange {
return { pos: range.pos, end };
}
export function collapseRangeToEnd(range: TextRange) {
return range.pos === range.end ? range : { pos: range.end, end: range.end };
/**
* Creates a new TextRange from a provided range with a new start position.
*
* @param range A TextRange.
* @param pos The new Start position.
*/
export function moveRangePos(range: TextRange, pos: number): TextRange {
return { pos, end: range.end };
}
/**
* Determines whether a TextRange has the same start and end positions.
*
* @param range A TextRange.
*/
export function isCollapsedRange(range: TextRange) {
return range.pos === range.end;
}
/**
* Creates a new TextRange from a provided range with its end position collapsed to its
* start position.
*
* @param range A TextRange.
*/
export function collapseRangeToStart(range: TextRange): TextRange {
return isCollapsedRange(range) ? range : moveRangeEnd(range, range.pos);
}
/**
* Creates a new TextRange from a provided range with its start position collapsed to its
* end position.
*
* @param range A TextRange.
*/
export function collapseRangeToEnd(range: TextRange): TextRange {
return isCollapsedRange(range) ? range : moveRangePos(range, range.end);
}
/**
* Creates a new TextRange for a token at the provides start position.
*
* @param pos The start position.
* @param token The token.
*/
export function createTokenRange(pos: number, token: SyntaxKind): TextRange {
return { pos, end: pos + tokenToString(token).length };
}
export function rangeIsOnSingleLine(range: TextRange, sourceFile: SourceFile) {
@@ -45,6 +45,6 @@ var x = function (a) { return a.toString(); };
var x2 = function (a) { return a.toString(); }; // Like iWithCallSignatures
var x2 = function (a) { return a; }; // Like iWithCallSignatures2
// With call signatures of mismatching parameter type
var x3 = function (a) { return a.toString(); };
var x3 = function (a) { /*here a should be any*/ return a.toString(); };
// With call signature count mismatch
var x4 = function (a) { return a.toString(); };
var x4 = function (a) { /*here a should be any*/ return a.toString(); };
@@ -110,9 +110,7 @@ var f8 = function (x, y, z) {
var f9 = function (a) { return a; };
var f10 = function (a) { return a; };
var f11 = function (a) { return a; };
var f12 = function (a) {
return a;
};
var f12 = function (a) { return a; };
// Should be valid.
var f11 = function (a) { return a; };
// Should be valid.