Skip to content

Preserve source newlines all the things #37814

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
59 changes: 46 additions & 13 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ namespace ts {
const printerOptions: PrinterOptions = {
removeComments: compilerOptions.removeComments,
newLine: compilerOptions.newLine,
preserveSourceNewlines: true,
noEmitHelpers: compilerOptions.noEmitHelpers,
module: compilerOptions.module,
target: compilerOptions.target,
Expand Down Expand Up @@ -2365,16 +2366,10 @@ namespace ts {

function emitParenthesizedExpression(node: ParenthesizedExpression) {
const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node);
const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(node, [node.expression], ListFormat.None);
if (leadingNewlines) {
writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false);
}
const indented = writeLineSeparatorsAndIndentBefore(node.expression, node);
emitExpression(node.expression);
const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(node, [node.expression], ListFormat.None);
if (trailingNewlines) {
writeLine(trailingNewlines);
}
decreaseIndentIf(leadingNewlines);
writeLineSeparatorsAfter(node.expression, node);
decreaseIndentIf(indented);
emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node);
}

Expand Down Expand Up @@ -3292,12 +3287,15 @@ namespace ts {
writePunctuation("<");

if (isJsxOpeningElement(node)) {
const indented = writeLineSeparatorsAndIndentBefore(node.tagName, node);
emitJsxTagName(node.tagName);
emitTypeArguments(node, node.typeArguments);
if (node.attributes.properties && node.attributes.properties.length > 0) {
writeSpace();
}
emit(node.attributes);
writeLineSeparatorsAfter(node.attributes, node);
decreaseIndentIf(indented);
}

writePunctuation(">");
Expand Down Expand Up @@ -3686,7 +3684,9 @@ namespace ts {
const statements = node.statements;
pushNameGenerationScope(node);
forEach(node.statements, generateNames);
emitHelpers(node);
if (emitHelpers(node)) {
writeLine();
}
const index = findIndex(statements, statement => !isPrologueDirective(statement));
emitTripleSlashDirectivesIfNeeded(node);
emitList(node, statements, ListFormat.MultiLine, index === -1 ? statements.length : index);
Expand Down Expand Up @@ -4301,6 +4301,7 @@ namespace ts {
return getEffectiveLines(
includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(
firstChild.pos,
parentNode.pos,
currentSourceFile!,
includeComments));
}
Expand All @@ -4322,12 +4323,12 @@ namespace ts {
// JsxText will be written with its leading whitespace, so don't add more manually.
return 0;
}
else if (!nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode) && previousNode.parent === nextNode.parent) {
else if (siblingNodePositionsAreComparable(previousNode, nextNode)) {
if (preserveSourceNewlines) {
return getEffectiveLines(
includeComments => getLinesBetweenRangeEndAndRangeStart(
previousNode,
nextNode,
getOriginalNode(previousNode),
getOriginalNode(nextNode),
currentSourceFile!,
includeComments));
}
Expand All @@ -4343,6 +4344,22 @@ namespace ts {
return format & ListFormat.MultiLine ? 1 : 0;
}

function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) {
if (previousNode.kind === SyntaxKind.NotEmittedStatement && nextNode.kind === SyntaxKind.NotEmittedStatement) {
return false;
}
if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode)) {
return false;
}

if (!previousNode.parent || !nextNode.parent) {
const previousParent = getOriginalNode(previousNode).parent;
return previousParent && previousParent === getOriginalNode(nextNode).parent;
}

return nextNode.pos >= previousNode.end;
}

function getClosingLineTerminatorCount(parentNode: TextRange, children: readonly Node[], format: ListFormat): number {
if (format & ListFormat.PreserveLines || preserveSourceNewlines) {
if (format & ListFormat.PreferNewLine) {
Expand All @@ -4358,6 +4375,7 @@ namespace ts {
return getEffectiveLines(
includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter(
lastChild.end,
parentNode.end,
currentSourceFile!,
includeComments));
}
Expand Down Expand Up @@ -4397,6 +4415,21 @@ namespace ts {
return lines;
}

function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean {
const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, [node], ListFormat.None);
if (leadingNewlines) {
writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false);
}
return !!leadingNewlines;
}

function writeLineSeparatorsAfter(node: Node, parent: Node) {
const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(parent, [node], ListFormat.None);
if (trailingNewlines) {
writeLine(trailingNewlines);
}
}

function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) {
if (nodeIsSynthesized(node)) {
const startsOnNewLine = getStartsOnNewLine(node);
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ namespace ts {
transformConstructorBody(constructor, node, extendsClauseElement, hasSynthesizedSuper)
);

setTextRange(constructorFunction, constructor || node);
setTextRange(setOriginalNode(constructorFunction, constructor), constructor || node);
if (extendsClauseElement) {
setEmitFlags(constructorFunction, EmitFlags.CapturesThis);
}
Expand Down
15 changes: 9 additions & 6 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2178,13 +2178,16 @@ namespace ts {
return undefined;
}

return setTextRange(
createExpressionStatement(
inlineExpressions(
map(variables, transformInitializedVariable)
)
return setOriginalNode(
setTextRange(
createExpressionStatement(
inlineExpressions(
map(variables, transformInitializedVariable)
)
),
node
),
node
node,
);
}
else {
Expand Down
14 changes: 7 additions & 7 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4776,19 +4776,19 @@ namespace ts {
return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos, /*stopAfterLineBreak*/ false, includeComments);
}

export function getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(pos: number, sourceFile: SourceFile, includeComments?: boolean) {
export function getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(pos: number, stopPos: number, sourceFile: SourceFile, includeComments?: boolean) {
const startPos = skipTrivia(sourceFile.text, pos, /*stopAfterLineBreak*/ false, includeComments);
const prevPos = getPreviousNonWhitespacePosition(startPos, sourceFile);
return getLinesBetweenPositions(sourceFile, prevPos || 0, startPos);
const prevPos = getPreviousNonWhitespacePosition(startPos, stopPos, sourceFile);
return getLinesBetweenPositions(sourceFile, prevPos ?? stopPos, startPos);
}

export function getLinesBetweenPositionAndNextNonWhitespaceCharacter(pos: number, sourceFile: SourceFile, includeComments?: boolean) {
export function getLinesBetweenPositionAndNextNonWhitespaceCharacter(pos: number, stopPos: number, sourceFile: SourceFile, includeComments?: boolean) {
const nextPos = skipTrivia(sourceFile.text, pos, /*stopAfterLineBreak*/ false, includeComments);
return getLinesBetweenPositions(sourceFile, pos, nextPos);
return getLinesBetweenPositions(sourceFile, pos, Math.min(stopPos, nextPos));
}

function getPreviousNonWhitespacePosition(pos: number, sourceFile: SourceFile) {
while (pos-- > 0) {
function getPreviousNonWhitespacePosition(pos: number, stopPos = 0, sourceFile: SourceFile) {
while (pos-- > stopPos) {
if (!isWhiteSpaceLike(sourceFile.text.charCodeAt(pos))) {
return pos;
}
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/1.0lib-noErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1158,4 +1158,5 @@ MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */

/// <reference no-default-lib="true"/>
Expand Down
19 changes: 17 additions & 2 deletions tests/baselines/reference/APISample_Watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,21 @@ watchMain();
* Please log a "breaking change" issue for any API breaking change affecting this issue
*/
exports.__esModule = true;

var ts = require("typescript");

var formatHost = {
getCanonicalFileName: function (path) { return path; },
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: function () { return ts.sys.newLine; }
};

function watchMain() {
var configPath = ts.findConfigFile(/*searchPath*/ "./", ts.sys.fileExists, "tsconfig.json");
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}

// TypeScript can use several different program creation "strategies":
// * ts.createEmitAndSemanticDiagnosticsBuilderProgram,
// * ts.createSemanticDiagnosticsBuilderProgram
Expand All @@ -111,7 +115,11 @@ function watchMain() {
// Between `createEmitAndSemanticDiagnosticsBuilderProgram` and `createSemanticDiagnosticsBuilderProgram`, the only difference is emit.
// For pure type-checking scenarios, or when another tool/process handles emit, using `createSemanticDiagnosticsBuilderProgram` may be more desirable.
// Note that there is another overload for `createWatchCompilerHost` that takes a set of root files.
var host = ts.createWatchCompilerHost(configPath, {}, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, reportDiagnostic, reportWatchStatusChanged);
var host = ts.createWatchCompilerHost(configPath, {}, ts.sys,
ts.createSemanticDiagnosticsBuilderProgram,
reportDiagnostic,
reportWatchStatusChanged);

// You can technically override any given hook on the host, though you probably don't need to.
// Note that we're assuming `origCreateProgram` and `origPostProgramCreate` doesn't use `this` at all.
var origCreateProgram = host.createProgram;
Expand All @@ -120,21 +128,28 @@ function watchMain() {
return origCreateProgram(rootNames, options, host, oldProgram);
};
var origPostProgramCreate = host.afterProgramCreate;

host.afterProgramCreate = function (program) {
console.log("** We finished making the program! **");
origPostProgramCreate(program);
};

// `createWatchProgram` creates an initial program, watches files, and updates the program over time.
ts.createWatchProgram(host);
}

function reportDiagnostic(diagnostic) {
console.error("Error", diagnostic.code, ":", ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()));
console.error("Error", diagnostic.code, ":",
ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine())
);
}

/**
* Prints a diagnostic every time the watch status changes.
* This is mainly for messages like "Starting compilation" or "Compilation completed".
*/
function reportWatchStatusChanged(diagnostic) {
console.info(ts.formatDiagnostic(diagnostic, formatHost));
}

watchMain();
7 changes: 7 additions & 0 deletions tests/baselines/reference/APISample_WatchWithDefaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ watchMain();
* Please log a "breaking change" issue for any API breaking change affecting this issue
*/
exports.__esModule = true;

var ts = require("typescript");

function watchMain() {
var configPath = ts.findConfigFile(/*searchPath*/ "./", ts.sys.fileExists, "tsconfig.json");
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}

// TypeScript can use several different program creation "strategies":
// * ts.createEmitAndSemanticDiagnosticsBuilderProgram,
// * ts.createSemanticDiagnosticsBuilderProgram
Expand All @@ -79,6 +82,7 @@ function watchMain() {
// For pure type-checking scenarios, or when another tool/process handles emit, using `createSemanticDiagnosticsBuilderProgram` may be more desirable.
// Note that there is another overload for `createWatchCompilerHost` that takes a set of root files.
var host = ts.createWatchCompilerHost(configPath, {}, ts.sys);

// You can technically override any given hook on the host, though you probably don't need to.
// Note that we're assuming `origCreateProgram` and `origPostProgramCreate` doesn't use `this` at all.
var origCreateProgram = host.createProgram;
Expand All @@ -87,11 +91,14 @@ function watchMain() {
return origCreateProgram(rootNames, options, host, oldProgram);
};
var origPostProgramCreate = host.afterProgramCreate;

host.afterProgramCreate = function (program) {
console.log("** We finished making the program! **");
origPostProgramCreate(program);
};

// `createWatchProgram` creates an initial program, watches files, and updates the program over time.
ts.createWatchProgram(host);
}

watchMain();
9 changes: 9 additions & 0 deletions tests/baselines/reference/APISample_WatchWithOwnWatchHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,34 @@ watchMain();
* Please log a "breaking change" issue for any API breaking change affecting this issue
*/
exports.__esModule = true;

var ts = require("typescript");

function watchMain() {
// get list of files and compiler options somehow
var files = [];
var options = {};

var host = {
rootFiles: files,
options: options,
useCaseSensitiveFileNames: function () { return ts.sys.useCaseSensitiveFileNames; },
getNewLine: function () { return ts.sys.newLine; },
getCurrentDirectory: ts.sys.getCurrentDirectory,
getDefaultLibFileName: function (options) { return ts.getDefaultLibFilePath(options); },

fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
directoryExists: ts.sys.directoryExists,
getDirectories: ts.sys.getDirectories,
readDirectory: ts.sys.readDirectory,
realpath: ts.sys.realpath,

watchFile: ts.sys.watchFile,
watchDirectory: ts.sys.watchDirectory,
createProgram: ts.createAbstractBuilder
};

// You can technically override any given hook on the host, though you probably don't need to.
// Note that we're assuming `origCreateProgram` and `origPostProgramCreate` doesn't use `this` at all.
var origCreateProgram = host.createProgram;
Expand All @@ -99,11 +105,14 @@ function watchMain() {
return origCreateProgram(rootNames, options, host, oldProgram);
};
var origPostProgramCreate = host.afterProgramCreate;

host.afterProgramCreate = function (program) {
console.log("** We finished making the program! **");
origPostProgramCreate(program);
};

// `createWatchProgram` creates an initial program, watches files, and updates the program over time.
ts.createWatchProgram(host);
}

watchMain();
5 changes: 5 additions & 0 deletions tests/baselines/reference/APISample_compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@ compile(process.argv.slice(2), {
*/
exports.__esModule = true;
exports.compile = void 0;

var ts = require("typescript");

function compile(fileNames, options) {
var program = ts.createProgram(fileNames, options);
var emitResult = program.emit();

var allDiagnostics = ts.getPreEmitDiagnostics(program);

allDiagnostics.forEach(function (diagnostic) {
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (!diagnostic.file) {
Expand All @@ -68,6 +72,7 @@ function compile(fileNames, options) {
var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character;
console.log(diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message);
});

var exitCode = emitResult.emitSkipped ? 1 : 0;
console.log("Process exiting with code '" + exitCode + "'.");
process.exit(exitCode);
Expand Down
Loading