diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 84eb1ac62c..72fad2f448 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -452,11 +452,15 @@ namespace ts { return node; } - export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement, location?: TextRange) { + export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement, location?: TextRange, options?: { startOnNewLine?: boolean; }) { const node = createNode(SyntaxKind.IfStatement, location); node.expression = expression; node.thenStatement = thenStatement; node.elseStatement = elseStatement; + if (options && options.startOnNewLine) { + node.startsOnNewLine = true; + } + return node; } diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 8b1c73abae..b229f7c153 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -1411,30 +1411,41 @@ const _super = (function (geti, seti) { } function shouldEmitBlockFunctionBodyOnSingleLine(parentNode: Node, body: Block) { + // We must emit a function body as a single-line body in the following case: + // * The body has NodeEmitFlags.SingleLine specified. + + // We must emit a function body as a multi-line body in the following cases: + // * The body is explicitly marked as multi-line. + // * A non-synthesized body's start and end position are on different lines. + // * Any statement in the body starts on a new line. + + if (getNodeEmitFlags(body) & NodeEmitFlags.SingleLine) { + return true; + } + if (body.multiLine) { return false; } - const originalNode = getOriginalNode(parentNode); - if (isFunctionLike(originalNode) && !nodeIsSynthesized(originalNode)) { - const body = originalNode.body; - if (isBlock(body)) { - if (rangeEndIsOnSameLineAsRangeStart(body, body)) { - for (const statement of body.statements) { - if (synthesizedNodeStartsOnNewLine(statement)) { - return false; - } - } - - return true; - } - } - else { - return rangeEndIsOnSameLineAsRangeStart((originalNode).equalsGreaterThanToken, originalNode.body); - } + if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile)) { + return false; } - return false; + if (shouldWriteLeadingLineTerminator(body, body.statements, ListFormat.PreserveLines) + || shouldWriteClosingLineTerminator(body, body.statements, ListFormat.PreserveLines)) { + return false; + } + + let previousStatement: Statement; + for (const statement of body.statements) { + if (shouldWriteSeparatingLineTerminator(previousStatement, statement, ListFormat.PreserveLines)) { + return false; + } + + previousStatement = statement; + } + + return true; } function emitBlockFunctionBody(parentNode: Node, body: Block) { @@ -1457,7 +1468,7 @@ const _super = (function (geti, seti) { const endingLine = writer.getLine(); emitLexicalEnvironment(endLexicalEnvironment(), /*newLine*/ startingLine !== endingLine); - emitLeadingComments(collapseTextRange(body.statements, TextRangeCollapse.CollapseToEnd)); + emitLeadingComments(collapseRangeToEnd(body.statements)); decreaseIndent(); } @@ -1738,7 +1749,7 @@ const _super = (function (geti, seti) { } function emitCaseOrDefaultClauseStatements(parentNode: Node, statements: NodeArray) { - if (statements.length === 1 && rangeStartPositionsAreOnSameLine(parentNode, statements[0])) { + if (statements.length === 1 && rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile)) { write(" "); emit(statements[0]); } @@ -1778,7 +1789,7 @@ const _super = (function (geti, seti) { // } // "comment1" is not considered to be leading comment for node.initializer // but rather a trailing comment on the previous node. - emitLeadingComments(node.initializer, getTrailingComments(collapseTextRange(node.initializer, TextRangeCollapse.CollapseToStart))); + emitLeadingComments(node.initializer, getTrailingComments(collapseRangeToStart(node.initializer))); emitExpression(node.initializer); } @@ -2236,13 +2247,13 @@ const _super = (function (geti, seti) { const firstChild = children[0]; if (firstChild === undefined) { - return !positionsAreOnSameLine(getStartPos(parentNode), parentNode.end); + return !rangeIsOnSingleLine(parentNode, currentSourceFile); } else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(firstChild)) { return synthesizedNodeStartsOnNewLine(firstChild, format); } else { - return !rangeStartPositionsAreOnSameLine(parentNode, firstChild); + return !rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile); } } else { @@ -2262,7 +2273,7 @@ const _super = (function (geti, seti) { return synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format); } else { - return !rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode); + return !rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile); } } else { @@ -2281,13 +2292,13 @@ const _super = (function (geti, seti) { const lastChild = lastOrUndefined(children); if (lastChild === undefined) { - return !positionsAreOnSameLine(getStartPos(parentNode), parentNode.end); + return !rangeIsOnSingleLine(parentNode, currentSourceFile); } else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(lastChild)) { return synthesizedNodeStartsOnNewLine(lastChild, format); } else { - return !rangeEndPositionsAreOnSameLine(parentNode, lastChild); + return !rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile); } } else { @@ -2304,30 +2315,10 @@ const _super = (function (geti, seti) { return startsOnNewLine; } + return (format & ListFormat.PreferNewLine) !== 0; } - function rangeStartPositionsAreOnSameLine(range1: TextRange, range2: TextRange) { - return positionsAreOnSameLine(getStartPos(range1), getStartPos(range2)); - } - - function rangeEndPositionsAreOnSameLine(range1: TextRange, range2: TextRange) { - return positionsAreOnSameLine(range1.end, range2.end); - } - - function rangeEndIsOnSameLineAsRangeStart(range1: TextRange, range2: TextRange) { - return positionsAreOnSameLine(range1.end, getStartPos(range2)); - } - - function positionsAreOnSameLine(pos1: number, pos2: number) { - return pos1 === pos2 || - getLineOfLocalPosition(currentSourceFile, pos1) === getLineOfLocalPosition(currentSourceFile, pos2); - } - - function getStartPos(range: TextRange) { - return range.pos === -1 ? -1 : skipTrivia(currentText, range.pos); - } - function needsIndentation(parent: Node, node1: Node, node2: Node): boolean { parent = skipSynthesizedParentheses(parent); node1 = skipSynthesizedParentheses(node1); @@ -2341,7 +2332,7 @@ const _super = (function (geti, seti) { return !nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2) - && !rangeEndIsOnSameLineAsRangeStart(node1, node2); + && !rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile); } function skipSynthesizedParentheses(node: Node) { @@ -2381,7 +2372,7 @@ const _super = (function (geti, seti) { function isSingleLineEmptyBlock(block: Block) { return !block.multiLine && block.statements.length === 0 - && rangeEndIsOnSameLineAsRangeStart(block, block); + && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile); } function getNotEmittedParent(node: Node): Node { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 29e44ca11f..17155281db 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -994,8 +994,8 @@ namespace ts { const start = new Date().getTime(); - // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permenantly. - if (Boolean(sys.getEnvironmentVariable("USE_TRANSFORMS"))) { + // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permanently. + if (/^(y(es)?|t(rue|ransforms?)?|1|\+)$/i.test(sys.getEnvironmentVariable("USE_TRANSFORMS"))) { options.experimentalTransforms = true; } diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 05c001a711..4c83312d38 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -564,7 +564,10 @@ namespace ts { ) ]), NodeEmitFlags.SingleLine - ) + ), + /*elseStatement*/ undefined, + /*location*/ undefined, + { startOnNewLine: true } ) ); } @@ -841,25 +844,62 @@ namespace ts { * @param node A function-like node. */ function transformFunctionBody(node: FunctionLikeDeclaration) { + let multiLine = false; // indicates whether the block *must* be emitted as multiple lines + let singleLine = false; // indicates whether the block *may* be emitted as a single line + const statements: Statement[] = []; startLexicalEnvironment(); addCaptureThisForNodeIfNeeded(statements, node); addDefaultValueAssignmentsIfNeeded(statements, node); addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false); + // If we added any generated statements, this must be a multi-line block. + if (!multiLine && statements.length > 0) { + multiLine = true; + } + const body = node.body; if (isBlock(body)) { addRange(statements, visitNodes(body.statements, visitor, isStatement)); + + // If the original body was a multi-line block, this must be a multi-line block. + if (!multiLine && body.multiLine) { + multiLine = true; + } } else { + Debug.assert(node.kind === SyntaxKind.ArrowFunction); + + const equalsGreaterThanToken = (node).equalsGreaterThanToken; + if (!nodeIsSynthesized(equalsGreaterThanToken) && !nodeIsSynthesized(body)) { + if (rangeEndIsOnSameLineAsRangeStart(equalsGreaterThanToken, body, currentSourceFile)) { + singleLine = true; + } + else { + multiLine = true; + } + } + const expression = visitNode(body, visitor, isExpression); if (expression) { - statements.push(createReturn(expression, /*location*/ body)); + statements.push(createReturn(expression)); } } - addRange(statements, endLexicalEnvironment()); - return createBlock(statements, node.body); + const lexicalEnvironment = endLexicalEnvironment(); + addRange(statements, lexicalEnvironment); + + // If we added any final generated statements, this must be a multi-line block + if (!multiLine && lexicalEnvironment && lexicalEnvironment.length) { + multiLine = true; + } + + const block = createBlock(statements, node.body, multiLine); + if (!multiLine && singleLine) { + setNodeEmitFlags(block, NodeEmitFlags.SingleLine); + } + + return block; } /** diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 8251c0c467..a595534f0b 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2228,7 +2228,7 @@ namespace ts { startLexicalEnvironment(); addNodes(statements, map(node.members, transformEnumMember)); addNodes(statements, endLexicalEnvironment()); - return createBlock(statements); + return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); } /** @@ -2455,7 +2455,7 @@ namespace ts { } addNodes(statements, endLexicalEnvironment()); - return createBlock(statements); + return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); } /** diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ebff65d6b1..e292503c1f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2925,19 +2925,41 @@ namespace ts { } } - export const enum TextRangeCollapse { - CollapseToStart, - CollapseToEnd, + export function collapseRangeToStart(range: TextRange) { + return range.pos === range.end ? range : { pos: range.pos, end: range.pos }; } - export function collapseTextRange(range: TextRange, collapse: TextRangeCollapse) { - if (range.pos === range.end) { - return range; - } + export function collapseRangeToEnd(range: TextRange) { + return range.pos === range.end ? range : { pos: range.end, end: range.end }; + } - return collapse === TextRangeCollapse.CollapseToStart - ? { pos: range.pos, end: range.pos } - : { pos: range.end, end: range.end }; + export function rangeIsOnSingleLine(range: TextRange, sourceFile: SourceFile) { + return rangeStartIsOnSameLineAsRangeEnd(range, range, sourceFile); + } + + export function rangeStartPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { + return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), getStartPositionOfRange(range2, sourceFile), sourceFile); + } + + export function rangeEndPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { + return positionsAreOnSameLine(range1.end, range2.end, sourceFile); + } + + export function rangeStartIsOnSameLineAsRangeEnd(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { + return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), range2.end, sourceFile); + } + + export function rangeEndIsOnSameLineAsRangeStart(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { + return positionsAreOnSameLine(range1.end, getStartPositionOfRange(range2, sourceFile), sourceFile); + } + + export function positionsAreOnSameLine(pos1: number, pos2: number, sourceFile: SourceFile) { + return pos1 === pos2 || + getLineOfLocalPosition(sourceFile, pos1) === getLineOfLocalPosition(sourceFile, pos2); + } + + export function getStartPositionOfRange(range: TextRange, sourceFile: SourceFile) { + return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos); } export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver) {