From cee49540902868070f60bda0bcf357cc016f5ceb Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 22 Apr 2016 16:28:26 -0700 Subject: [PATCH] Added more control over sourcemap/comment emit and fixed a number of source map emit issues. --- src/compiler/comments.ts | 24 +++-- src/compiler/factory.ts | 75 +++++++------- src/compiler/printer.ts | 21 ++-- src/compiler/sourcemap.ts | 34 ++++--- src/compiler/transformer.ts | 113 ++++++++++++++++----- src/compiler/transformers/destructuring.ts | 2 +- src/compiler/transformers/es6.ts | 32 +++--- src/compiler/transformers/ts.ts | 60 +++++++---- src/compiler/types.ts | 75 +++++++++++--- src/compiler/utilities.ts | 12 ++- 10 files changed, 298 insertions(+), 150 deletions(-) diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index d07e1391734..5d3d84963b5 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -77,10 +77,15 @@ namespace ts { emitTrailingDetachedComments }; - function getLeadingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange): CommentRange[]; function getLeadingComments(range: TextRange): CommentRange[]; - function getLeadingComments(range: TextRange | Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange) { - if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(range)) { + function getLeadingComments(node: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange): CommentRange[]; + function getLeadingComments(nodeOrRange: TextRange | Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange) { + let range = nodeOrRange; + if (getCustomCommentRangeForNodeCallback) { + range = getCustomCommentRangeForNodeCallback(nodeOrRange) || range; + } + + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(nodeOrRange)) { // If the node will not be emitted in JS, remove all the comments (normal, // pinned and `///`) associated with the node, unless it is a triple slash // comment at the top of the file. @@ -100,10 +105,6 @@ namespace ts { return undefined; } - if (getCustomCommentRangeForNodeCallback) { - range = getCustomCommentRangeForNodeCallback(range) || range; - } - return getLeadingCommentsOfPosition(range.pos); } @@ -123,15 +124,16 @@ namespace ts { return false; } - function getTrailingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange): CommentRange[]; function getTrailingComments(range: TextRange): CommentRange[]; - function getTrailingComments(range: TextRange | Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange) { - if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(range)) { + function getTrailingComments(node: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange): CommentRange[]; + function getTrailingComments(nodeOrRange: TextRange | Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean, getCustomCommentRangeForNodeCallback?: (node: Node) => TextRange) { + let range = nodeOrRange; + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(nodeOrRange)) { return undefined; } if (getCustomCommentRangeForNodeCallback) { - range = getCustomCommentRangeForNodeCallback(range) || range; + range = getCustomCommentRangeForNodeCallback(nodeOrRange) || range; } return getTrailingCommentsOfPosition(range.end); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c4ee5c33fe9..affd9baf16e 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -8,7 +8,7 @@ namespace ts { let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags): Node { + function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags, emitOptions?: NodeEmitOptions): Node { const ConstructorForKind = kind === SyntaxKind.SourceFile ? (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor())) : (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor())); @@ -21,6 +21,10 @@ namespace ts { node.flags = flags; } + if (emitOptions) { + node.emitOptions = emitOptions; + } + return node; } @@ -64,11 +68,11 @@ namespace ts { /** * Creates a shallow, memberwise clone of a node with no source map location. */ - export function getSynthesizedClone(node: T): T { + export function getSynthesizedClone(node: T, emitOptions?: NodeEmitOptions): T { // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- // properties (to skip members already defined on the shared prototype). - const clone = createNode(node.kind, /*location*/ undefined); + const clone = createNode(node.kind, /*location*/ undefined, /*flags*/ undefined, emitOptions); clone.flags = node.flags; clone.original = node; @@ -86,29 +90,19 @@ namespace ts { /** * Creates a shallow, memberwise clone of a node for mutation. */ - export function getMutableClone(node: T): T { - const clone = getSynthesizedClone(node); + export function getMutableClone(node: T, emitOptions?: NodeEmitOptions): T { + const clone = getSynthesizedClone(node, emitOptions); clone.pos = node.pos; clone.end = node.end; clone.parent = node.parent; return clone; } - /** - * Creates a shallow, memberwise clone of a node at the specified source map location. - */ - export function getRelocatedClone(node: T, location: TextRange): T { - const clone = getSynthesizedClone(node); - clone.pos = location.pos; - clone.end = location.end; - return clone; - } - /** * Gets a clone of a node with a unique node ID. */ - export function getUniqueClone(node: T): T { - const clone = getMutableClone(node); + export function getUniqueClone(node: T, emitOptions?: NodeEmitOptions): T { + const clone = getMutableClone(node, emitOptions); clone.id = undefined; getNodeId(clone); return clone; @@ -116,26 +110,26 @@ namespace ts { // Literals - export function createLiteral(textSource: StringLiteral | Identifier, location?: TextRange): StringLiteral; - export function createLiteral(value: string, location?: TextRange): StringLiteral; - export function createLiteral(value: number, location?: TextRange): LiteralExpression; - export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression; - export function createLiteral(value: string | number | boolean | StringLiteral | Identifier, location?: TextRange): PrimaryExpression { + export function createLiteral(textSource: StringLiteral | Identifier, location?: TextRange, emitOptions?: NodeEmitOptions): StringLiteral; + export function createLiteral(value: string, location?: TextRange, emitOptions?: NodeEmitOptions): StringLiteral; + export function createLiteral(value: number, location?: TextRange, emitOptions?: NodeEmitOptions): LiteralExpression; + export function createLiteral(value: string | number | boolean, location?: TextRange, emitOptions?: NodeEmitOptions): PrimaryExpression; + export function createLiteral(value: string | number | boolean | StringLiteral | Identifier, location?: TextRange, emitOptions?: NodeEmitOptions): PrimaryExpression { if (typeof value === "number") { - const node = createNode(SyntaxKind.NumericLiteral, location); + const node = createNode(SyntaxKind.NumericLiteral, location, /*flags*/ undefined, emitOptions); node.text = value.toString(); return node; } else if (typeof value === "boolean") { - return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location); + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location, /*flags*/ undefined, emitOptions); } else if (typeof value === "string") { - const node = createNode(SyntaxKind.StringLiteral, location); + const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined, emitOptions); node.text = value; return node; } else { - const node = createNode(SyntaxKind.StringLiteral, location); + const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined, emitOptions); node.textSourceNode = value; node.text = value.text; return node; @@ -256,10 +250,14 @@ namespace ts { } export function createParameter(name: string | Identifier | BindingPattern, initializer?: Expression, location?: TextRange) { + return createParameterWithDotDotDotToken(/*dotDotDotToken*/ undefined, name, initializer, location); + } + + export function createParameterWithDotDotDotToken(dotDotDotToken: Node, name: string | Identifier | BindingPattern, initializer?: Expression, location?: TextRange) { const node = createNode(SyntaxKind.Parameter, location); node.decorators = undefined; node.modifiers = undefined; - node.dotDotDotToken = undefined; + node.dotDotDotToken = dotDotDotToken; node.name = typeof name === "string" ? createIdentifier(name) : name; node.questionToken = undefined; node.type = undefined; @@ -287,8 +285,8 @@ namespace ts { return node; } - export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange) { - const node = createNode(SyntaxKind.PropertyAccessExpression, location); + export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange, emitOptions?: NodeEmitOptions) { + const node = createNode(SyntaxKind.PropertyAccessExpression, location, /*flags*/ undefined, emitOptions); node.expression = parenthesizeForAccess(expression); node.dotToken = createSynthesizedNode(SyntaxKind.DotToken); node.name = typeof name === "string" ? createIdentifier(name) : name; @@ -738,7 +736,7 @@ namespace ts { export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { if (isIdentifier(memberName)) { - return createPropertyAccess(target, getSynthesizedClone(memberName), location); + return createPropertyAccess(target, memberName, location, { flags: NodeEmitFlags.NoNestedSourceMaps | NodeEmitFlags.Merge }); } else if (isComputedPropertyName(memberName)) { return createElementAccess(target, memberName.expression, location); @@ -749,8 +747,7 @@ namespace ts { } export function createRestParameter(name: string | Identifier) { - const node = createParameter(name, /*initializer*/ undefined); - node.dotDotDotToken = createSynthesizedNode(SyntaxKind.DotDotDotToken); + const node = createParameterWithDotDotDotToken(createSynthesizedNode(SyntaxKind.DotDotDotToken), name, /*initializer*/ undefined); return node; } @@ -1279,10 +1276,16 @@ namespace ts { : getSynthesizedClone(node); } - export function createExpressionForPropertyName(memberName: PropertyName, location?: TextRange): Expression { - return isIdentifier(memberName) ? createLiteral(memberName.text, location) - : isComputedPropertyName(memberName) ? getRelocatedClone(memberName.expression, location || synthesizedLocation) - : getRelocatedClone(memberName, location || synthesizedLocation); + export function createExpressionForPropertyName(memberName: PropertyName, emitOptions: NodeEmitOptions): Expression { + if (isIdentifier(memberName)) { + return createLiteral(memberName, /*location*/ undefined, emitOptions); + } + else if (isComputedPropertyName(memberName)) { + return getMutableClone(memberName.expression, emitOptions); + } + else { + return getMutableClone(memberName, emitOptions); + } } // Utilities diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index d30112719b4..dbd02ef1542 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -172,7 +172,8 @@ const _super = (function (geti, seti) { let context: TransformationContext; let getNodeEmitFlags: (node: Node) => NodeEmitFlags; let setNodeEmitFlags: (node: Node, flags: NodeEmitFlags) => void; - let getNodeCustomCommentRange: (node: Node) => TextRange; + let getCommentRange: (node: Node) => TextRange; + let getSourceMapRange: (node: Node) => TextRange; let isSubstitutionEnabled: (node: Node) => boolean; let isEmitNotificationEnabled: (node: Node) => boolean; let onSubstituteNode: (node: Node, isExpression: boolean) => Node; @@ -233,7 +234,8 @@ const _super = (function (geti, seti) { getNodeEmitFlags = undefined; setNodeEmitFlags = undefined; - getNodeCustomCommentRange = undefined; + getCommentRange = undefined; + getSourceMapRange = undefined; isSubstitutionEnabled = undefined; isEmitNotificationEnabled = undefined; onSubstituteNode = undefined; @@ -253,7 +255,8 @@ const _super = (function (geti, seti) { context = _context; getNodeEmitFlags = context.getNodeEmitFlags; setNodeEmitFlags = context.setNodeEmitFlags; - getNodeCustomCommentRange = context.getNodeCustomCommentRange; + getCommentRange = context.getCommentRange; + getSourceMapRange = context.getSourceMapRange; isSubstitutionEnabled = context.isSubstitutionEnabled; isEmitNotificationEnabled = context.isEmitNotificationEnabled; onSubstituteNode = context.onSubstituteNode; @@ -337,12 +340,12 @@ const _super = (function (geti, seti) { function emitNodeWithWorker(node: Node, emitWorker: (node: Node) => void) { if (node) { - const leadingComments = getLeadingComments(node, shouldSkipLeadingCommentsForNode, getNodeCustomCommentRange); - const trailingComments = getTrailingComments(node, shouldSkipTrailingCommentsForNode, getNodeCustomCommentRange); + const leadingComments = getLeadingComments(node, shouldSkipLeadingCommentsForNode, getCommentRange); + const trailingComments = getTrailingComments(node, shouldSkipTrailingCommentsForNode, getCommentRange); emitLeadingComments(node, leadingComments); - emitStart(node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren); + emitStart(node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); emitWorker(node); - emitEnd(node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren); + emitEnd(node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); emitTrailingComments(node, trailingComments); } } @@ -2429,9 +2432,9 @@ const _super = (function (geti, seti) { function writeTokenNode(node: Node) { if (node) { - emitStart(node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren); + emitStart(node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); writeTokenText(node.kind); - emitEnd(node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren); + emitEnd(node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); } } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 832a89a4c56..d3e7bf679f5 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -7,9 +7,9 @@ namespace ts { setSourceFile(sourceFile: SourceFile): void; emitPos(pos: number, contextNode: Node, shouldIgnorePosCallback: (node: Node) => boolean): void; emitPos(pos: number): void; - emitStart(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; + emitStart(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange): void; emitStart(range: TextRange): void; - emitEnd(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; + emitEnd(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange): void; emitEnd(range: TextRange): void; /*@deprecated*/ changeEmitSourcePos(): void; /*@deprecated*/ stopOverridingSpan(): void; @@ -36,8 +36,8 @@ namespace ts { nullSourceMapWriter = { getSourceMapData(): SourceMapData { return undefined; }, setSourceFile(sourceFile: SourceFile): void { }, - emitStart(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void { }, - emitEnd(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void { }, + emitStart(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange): void { }, + emitEnd(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange): void { }, emitPos(pos: number): void { }, changeEmitSourcePos(): void { }, stopOverridingSpan(): void { }, @@ -327,26 +327,36 @@ namespace ts { return range.pos !== -1 ? skipTrivia(currentSourceFile.text, rangeHasDecorators ? (range as Node).decorators.end : range.pos) : -1; } - function emitStart(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; function emitStart(range: TextRange): void; - function emitStart(range: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean) { - if (!(shouldIgnoreNodeCallback && shouldIgnoreNodeCallback(range))) { + function emitStart(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange): void; + function emitStart(nodeOrRange: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange) { + let range = nodeOrRange; + if (!(shouldIgnoreNodeCallback && shouldIgnoreNodeCallback(nodeOrRange))) { + if (getCustomSourceMapRangeForNode) { + range = getCustomSourceMapRangeForNode(nodeOrRange) || range; + } + emitPos(getStartPos(range)); } - if (shouldIgnoreChildrenCallback && shouldIgnoreChildrenCallback(range)) { + if (shouldIgnoreChildrenCallback && shouldIgnoreChildrenCallback(nodeOrRange)) { disable(); } } - function emitEnd(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean): void; function emitEnd(range: TextRange): void; - function emitEnd(range: TextRange, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean) { - if (shouldIgnoreChildrenCallback && shouldIgnoreChildrenCallback(range)) { + function emitEnd(node: Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange): void; + function emitEnd(nodeOrRange: TextRange | Node, shouldIgnoreNodeCallback?: (node: Node) => boolean, shouldIgnoreChildrenCallback?: (node: Node) => boolean, getCustomSourceMapRangeForNode?: (node: Node) => TextRange) { + let range = nodeOrRange; + if (shouldIgnoreChildrenCallback && shouldIgnoreChildrenCallback(nodeOrRange)) { enable(); } - if (!(shouldIgnoreNodeCallback && shouldIgnoreNodeCallback(range))) { + if (!(shouldIgnoreNodeCallback && shouldIgnoreNodeCallback(nodeOrRange))) { + if (getCustomSourceMapRangeForNode) { + range = getCustomSourceMapRangeForNode(nodeOrRange) || range; + } + emitPos(range.end); } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index aeba23549cf..d4fc65395a4 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -54,8 +54,7 @@ namespace ts { * @param transforms An array of Transformers. */ export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]) { - const nodeEmitFlags: NodeEmitFlags[] = []; - const nodeCustomCommentRange: TextRange[] = []; + const nodeEmitOptions: NodeEmitOptions[] = []; const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); @@ -73,8 +72,10 @@ namespace ts { getEmitHost: () => host, getNodeEmitFlags, setNodeEmitFlags, - getNodeCustomCommentRange, - setNodeCustomCommentRange, + getSourceMapRange, + setSourceMapRange, + getCommentRange, + setCommentRange, hoistVariableDeclaration, hoistFunctionDeclaration, startLexicalEnvironment, @@ -107,10 +108,16 @@ namespace ts { return transformation(sourceFile); } + /** + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. + */ function enableSubstitution(kind: SyntaxKind) { enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; } + /** + * Determines whether expression substitutions are enabled for the provided node. + */ function isSubstitutionEnabled(node: Node) { return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0; } @@ -126,10 +133,17 @@ namespace ts { return node; } + /** + * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. + */ function enableEmitNotification(kind: SyntaxKind) { enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; } + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ function isEmitNotificationEnabled(node: Node) { return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 || (getNodeEmitFlags(node) & NodeEmitFlags.AdviseOnEmitNode) !== 0; @@ -154,51 +168,94 @@ namespace ts { emit(node); } + function getNodeEmitOptions(node: Node, createIfMissing: boolean) { + // Keeps track of the nearest set of options + let options: NodeEmitOptions; + let currentNode = node; + while (currentNode) { + const currentOptions = currentNode.emitOptions || nodeEmitOptions[getNodeId(currentNode)]; + if (currentOptions) { + options = currentOptions; + break; + } + + currentNode = currentNode.original; + } + + if (currentNode !== node && createIfMissing) { + options = options ? clone(options) : { }; + if (isSourceTreeNode(node)) { + nodeEmitOptions[getNodeId(node)] = options; + } + else { + node.emitOptions = options; + } + } + + // Merge with previous options on get. + if (options && options.flags & NodeEmitFlags.Merge) { + const previousOptions = getNodeEmitOptions(currentNode.original, /*createIfMissing*/ false); + if (previousOptions) { + options.flags = (options.flags | previousOptions.flags) & ~NodeEmitFlags.Merge; + if (!options.sourceMapRange && (options.flags & NodeEmitFlags.NoSourceMap) === 0) { + options.sourceMapRange = previousOptions.sourceMapRange; + } + + if (!options.commentRange && (options.flags & NodeEmitFlags.NoComments) === 0) { + options.commentRange = previousOptions.commentRange; + } + } + } + + return options; + } + /** * Gets flags that control emit behavior of a node. */ function getNodeEmitFlags(node: Node) { - while (node) { - const nodeId = node.id; - if (nodeId && nodeEmitFlags[nodeId] !== undefined) { - return nodeEmitFlags[nodeId]; - } - - node = node.original; - } - - return undefined; + const options = getNodeEmitOptions(node, /*createIfMissing*/ false); + return options && options.flags; } /** * Sets flags that control emit behavior of a node. */ function setNodeEmitFlags(node: T, flags: NodeEmitFlags) { - nodeEmitFlags[getNodeId(node)] = flags; + getNodeEmitOptions(node, /*createIfMissing*/ true).flags = flags; + return node; + } + + /** + * Gets a custom text range to use when emitting source maps. + */ + function getSourceMapRange(node: Node) { + const options = getNodeEmitOptions(node, /*createIfMissing*/ false); + return options && options.sourceMapRange; + } + + /** + * Sets a custom text range to use when emitting source maps. + */ + function setSourceMapRange(node: T, range: TextRange) { + getNodeEmitOptions(node, /*createIfMissing*/ true).sourceMapRange = range; return node; } /** * Gets a custom text range to use when emitting comments. */ - function getNodeCustomCommentRange(node: Node) { - while (node) { - const nodeId = node.id; - if (nodeId && nodeCustomCommentRange[nodeId] !== undefined) { - return nodeCustomCommentRange[nodeId]; - } - - node = node.original; - } - - return undefined; + function getCommentRange(node: Node) { + const options = getNodeEmitOptions(node, /*createIfMissing*/ false); + return options && options.commentRange; } /** * Sets a custom text range to use when emitting comments. */ - function setNodeCustomCommentRange(node: Node, range: TextRange) { - nodeCustomCommentRange[getNodeId(node)] = range; + function setCommentRange(node: T, range: TextRange) { + getNodeEmitOptions(node, /*createIfMissing*/ true).commentRange = range; + return node; } /** diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index c8ee58d8afc..5e9e51da951 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -251,7 +251,7 @@ namespace ts { emitArrayLiteralAssignment(target, value, location); } else { - const name = getRelocatedClone(target, /*location*/ target); + const name = getSynthesizedClone(target, { sourceMapRange: target, commentRange: target }); emitAssignment(name, value, location, /*original*/ undefined); } } diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 5614b6dbcae..a61667b1be1 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -145,6 +145,8 @@ namespace ts { hoistVariableDeclaration, getNodeEmitFlags, setNodeEmitFlags, + setCommentRange, + setSourceMapRange } = context; const resolver = context.getEmitResolver(); @@ -1055,18 +1057,19 @@ namespace ts { function addCaptureThisForNodeIfNeeded(statements: Statement[], node: Node): void { if (node.transformFlags & TransformFlags.ContainsCapturedLexicalThis && node.kind !== SyntaxKind.ArrowFunction) { enableSubstitutionsForCapturedThis(); - statements.push( - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration( - "_this", - createThis() - ) - ]), - /*location*/ node - ) + const captureThisStatement = createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + "_this", + createThis() + ) + ]) ); + + setNodeEmitFlags(captureThisStatement, NodeEmitFlags.NoComments); + setSourceMapRange(captureThisStatement, node); + statements.push(captureThisStatement); } } @@ -1174,11 +1177,8 @@ namespace ts { function transformAccessorsToExpression(receiver: LeftHandSideExpression, { firstAccessor, getAccessor, setAccessor }: AllAccessorDeclarations): Expression { // To align with source maps in the old emitter, the receiver and property name // arguments are both mapped contiguously to the accessor name. - const target = getSynthesizedClone(receiver); - target.pos = firstAccessor.name.pos; - - const propertyName = createExpressionForPropertyName(visitNode(firstAccessor.name, visitor, isPropertyName)); - propertyName.end = firstAccessor.name.end; + const target = getMutableClone(receiver, { flags: NodeEmitFlags.Merge | NodeEmitFlags.NoComments, sourceMapRange: moveRangeEnd(firstAccessor.name, -1) }); + const propertyName = createExpressionForPropertyName(visitNode(firstAccessor.name, visitor, isPropertyName), { flags: NodeEmitFlags.NoComments, sourceMapRange: moveRangePos(firstAccessor.name, -1) }); let getAccessorExpression: FunctionExpression; if (getAccessor) { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index e6d9e40baa9..0afba8e04a8 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -24,8 +24,10 @@ namespace ts { export function transformTypeScript(context: TransformationContext) { const { - setNodeEmitFlags, getNodeEmitFlags, + setNodeEmitFlags, + setCommentRange, + setSourceMapRange, startLexicalEnvironment, endLexicalEnvironment, hoistVariableDeclaration, @@ -911,16 +913,18 @@ namespace ts { */ function transformParameterWithPropertyAssignment(node: ParameterDeclaration) { Debug.assert(isIdentifier(node.name)); - + const name = node.name as Identifier; + const propertyName = getMutableClone(name, { flags: NodeEmitFlags.NoComments | NodeEmitFlags.NoSourceMap }); + const localName = getMutableClone(name, { flags: NodeEmitFlags.NoComments }); return startOnNewLine( createStatement( createAssignment( createPropertyAccess( createThis(), - getMutableClone(node.name), + propertyName, /*location*/ node.name ), - getUniqueClone(node.name) + localName ), /*location*/ node ) @@ -1011,8 +1015,13 @@ namespace ts { function transformInitializedProperty(node: ClassExpression | ClassDeclaration, property: PropertyDeclaration, receiver: LeftHandSideExpression, location?: TextRange) { const propertyName = visitPropertyNameOfClassElement(property); const initializer = visitNode(property.initializer, visitor, isExpression); + const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); + if (!isComputedPropertyName(propertyName)) { + setNodeEmitFlags(memberAccess, NodeEmitFlags.NoNestedSourceMaps); + } + return createAssignment( - createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName), + memberAccess, initializer, location ); @@ -1910,7 +1919,7 @@ namespace ts { visitPropertyNameOfClassElement(node), visitNodes(node.parameters, visitor, isParameter), transformFunctionBody(node), - /*location*/ getUndecoratedRange(node) + /*location*/ node ); setOriginalNode(method, node); @@ -1918,7 +1927,8 @@ namespace ts { // While we emit the source map for the node after skipping the decorators, // we need to emit the comments for the original range. if (node.decorators) { - context.setNodeCustomCommentRange(method, node); + setCommentRange(method, node); + setSourceMapRange(method, getUndecoratedRange(node)); } return method; @@ -1952,7 +1962,7 @@ namespace ts { visitNodes(node.modifiers, visitor, isModifier), visitPropertyNameOfClassElement(node), node.body ? visitEachChild(node.body, visitor, context) : createBlock([]), - /*location*/ getUndecoratedRange(node) + /*location*/ node ); setOriginalNode(accessor, node); @@ -1960,7 +1970,8 @@ namespace ts { // While we emit the source map for the node after skipping the decorators, // we need to emit the comments for the original range. if (node.decorators) { - context.setNodeCustomCommentRange(accessor, node); + setCommentRange(accessor, node); + setSourceMapRange(accessor, getUndecoratedRange(node)); } return accessor; @@ -1985,7 +1996,7 @@ namespace ts { visitPropertyNameOfClassElement(node), visitNode(firstOrUndefined(node.parameters), visitor, isParameter), node.body ? visitEachChild(node.body, visitor, context) : createBlock([]), - /*location*/ getUndecoratedRange(node) + /*location*/ node ); setOriginalNode(accessor, node); @@ -1993,7 +2004,8 @@ namespace ts { // While we emit the source map for the node after skipping the decorators, // we need to emit the comments for the original range. if (node.decorators) { - context.setNodeCustomCommentRange(accessor, node); + setCommentRange(accessor, node); + setSourceMapRange(accessor, getUndecoratedRange(node)); } return accessor; @@ -2176,21 +2188,27 @@ namespace ts { * @param node The parameter declaration node. */ function visitParameter(node: ParameterDeclaration) { - if (node.name && (node.name as Identifier).originalKeywordKind === SyntaxKind.ThisKeyword) { + if (node.name && isIdentifier(node.name) && (node.name as Identifier).originalKeywordKind === SyntaxKind.ThisKeyword) { return undefined; } - const clone = getMutableClone(node); - clone.decorators = undefined; - clone.modifiers = undefined; - clone.questionToken = undefined; - clone.type = undefined; + const parameter = createParameterWithDotDotDotToken( + node.dotDotDotToken, + visitNode(node.name, visitor, isBindingName), + visitNode(node.initializer, visitor, isExpression), + /*location*/ node + ); + + setOriginalNode(parameter, node); + + // While we emit the source map for the node after skipping the decorators, + // we need to emit the comments for the original range. if (node.decorators) { - setTextRange(clone, getUndecoratedRange(node)); + setCommentRange(parameter, node); + setSourceMapRange(parameter, getUndecoratedRange(node)); } - aggregateTransformFlags(clone); - return visitEachChild(clone, visitor, context); + return parameter; } /** @@ -3097,7 +3115,7 @@ namespace ts { if (declaration) { const classAlias = currentDecoratedClassAliases[getNodeId(declaration)]; if (classAlias) { - return getRelocatedClone(classAlias, /*location*/ node); + return getSynthesizedClone(classAlias, { sourceMapRange: node, commentRange: node }); } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 52c178ab748..67a9c209699 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -469,6 +469,7 @@ namespace ts { /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding) /* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes) + /* @internal */ emitOptions?: NodeEmitOptions; // Options used to control node emit (used by transforms, should never be set directly on a source tree node) } export interface NodeArray extends Array, TextRange { @@ -2941,16 +2942,34 @@ namespace ts { ExportName = 1 << 16, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). LocalName = 1 << 17, // Ensure an export prefix is not added for an identifier that points to an exported declaration. Indented = 1 << 18, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). + Merge = 1 << 19, // When getting emit options, merge with existing emit options. // SourceMap Specialization. // TODO(rbuckton): These should be removed once source maps are aligned with the old // emitter and new baselines are taken. This exists solely to // align with the old emitter. - SourceMapEmitOpenBraceAsToken = 1 << 19, // Emits the open brace of a block function body as a source mapped token. - SourceMapAdjustRestParameterLoop = 1 << 20, // Emits adjusted source map positions for a ForStatement generated when transforming a rest parameter for ES5/3. + SourceMapEmitOpenBraceAsToken = 1 << 20, // Emits the open brace of a block function body as a source mapped token. + SourceMapAdjustRestParameterLoop = 1 << 21, // Emits adjusted source map positions for a ForStatement generated when transforming a rest parameter for ES5/3. + } + + /* @internal */ + export interface NodeEmitOptions { + /** + * Specifies a custom range to use when emitting source maps. + */ + sourceMapRange?: TextRange; + /** + * Specifies a custom range to use when emitting comments. + */ + commentRange?: TextRange; + /** + * Specifies flags to use to customize emit. + */ + flags?: NodeEmitFlags; } /** Additional context provided to `visitEachChild` */ + /* @internal */ export interface LexicalEnvironment { /** Starts a new lexical environment. */ startLexicalEnvironment(): void; @@ -2964,22 +2983,54 @@ namespace ts { getCompilerOptions(): CompilerOptions; getEmitResolver(): EmitResolver; getEmitHost(): EmitHost; + + /** + * Gets flags used to customize later transformations or emit. + */ getNodeEmitFlags(node: Node): NodeEmitFlags; + + /** + * Sets flags used to customize later transformations or emit. + */ setNodeEmitFlags(node: T, flags: NodeEmitFlags): T; - getNodeCustomCommentRange(node: Node): TextRange; - setNodeCustomCommentRange(node: Node, range: TextRange): void; + + /** + * Gets the TextRange to use for source maps for the node. + */ + getSourceMapRange(node: Node): TextRange; + + /** + * Sets the TextRange to use for source maps for the node. + */ + setSourceMapRange(node: T, range: TextRange): T; + + /** + * Gets the TextRange to use for comments for the node. + */ + getCommentRange(node: Node): TextRange; + + /** + * Sets the TextRange to use for comments for the node. + */ + setCommentRange(node: T, range: TextRange): T; + + /** + * Hoists a function declaration to the containing scope. + */ hoistFunctionDeclaration(node: FunctionDeclaration): void; + + /** + * Hoists a variable declaration to the containing scope. + */ hoistVariableDeclaration(node: Identifier): void; /** - * Enables expression substitutions in the pretty printer for - * the provided SyntaxKind. + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ enableSubstitution(kind: SyntaxKind): void; /** - * Determines whether expression substitutions are enabled for the - * provided node. + * Determines whether expression substitutions are enabled for the provided node. */ isSubstitutionEnabled(node: Node): boolean; @@ -2990,14 +3041,14 @@ namespace ts { onSubstituteNode?: (node: Node, isExpression: boolean) => Node; /** - * Enables before/after emit notifications in the pretty printer for - * the provided SyntaxKind. + * Enables before/after emit notifications in the pretty printer for the provided + * SyntaxKind. */ enableEmitNotification(kind: SyntaxKind): void; /** - * Determines whether before/after emit notifications should be raised - * in the pretty printer when it emits a node. + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. */ isEmitNotificationEnabled(node: Node): boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 73d09dd4765..98767eb0659 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1830,12 +1830,16 @@ namespace ts { return node; } + export function isSourceTreeNode(node: Node): boolean { + return node.original === undefined + && (node.parent !== undefined || node.kind === SyntaxKind.SourceFile); + } + export function getSourceTreeNode(node: Node): Node { node = getOriginalNode(node); - if (node) { - if (node.parent || node.kind === SyntaxKind.SourceFile) { - return node; - } + + if (node && (node.parent !== undefined || node.kind === SyntaxKind.SourceFile)) { + return node; } return undefined;