Skip to content

Commit 11a62cb

Browse files
authored
Merge pull request #31801 from andrewbranch/semicolons
Detect semicolons before writing from TextChanges
2 parents 327bc3c + 6282c9f commit 11a62cb

36 files changed

+303
-210
lines changed

src/services/textChanges.ts

+154-128
Original file line numberDiff line numberDiff line change
@@ -847,9 +847,10 @@ namespace ts.textChanges {
847847

848848
/** Note: output node may be mutated input node. */
849849
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
850-
const writer = new Writer(newLineCharacter);
850+
const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile);
851+
const writer = createWriter(newLineCharacter, omitTrailingSemicolon);
851852
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
852-
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
853+
createPrinter({ newLine, neverAsciiEscape: true, omitTrailingSemicolon }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
853854
return { text: writer.getText(), node: assignPositionsToNode(node) };
854855
}
855856
}
@@ -887,143 +888,168 @@ namespace ts.textChanges {
887888
return nodeArray;
888889
}
889890

890-
class Writer implements EmitTextWriter, PrintHandlers {
891-
private lastNonTriviaPosition = 0;
892-
private readonly writer: EmitTextWriter;
893-
894-
public readonly onEmitNode: PrintHandlers["onEmitNode"];
895-
public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"];
896-
public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"];
897-
public readonly onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"];
898-
public readonly onAfterEmitToken: PrintHandlers["onAfterEmitToken"];
899-
900-
constructor(newLine: string) {
901-
this.writer = createTextWriter(newLine);
902-
this.onEmitNode = (hint, node, printCallback) => {
903-
if (node) {
904-
setPos(node, this.lastNonTriviaPosition);
905-
}
906-
printCallback(hint, node);
907-
if (node) {
908-
setEnd(node, this.lastNonTriviaPosition);
909-
}
910-
};
911-
this.onBeforeEmitNodeArray = nodes => {
912-
if (nodes) {
913-
setPos(nodes, this.lastNonTriviaPosition);
914-
}
915-
};
916-
this.onAfterEmitNodeArray = nodes => {
917-
if (nodes) {
918-
setEnd(nodes, this.lastNonTriviaPosition);
919-
}
920-
};
921-
this.onBeforeEmitToken = node => {
922-
if (node) {
923-
setPos(node, this.lastNonTriviaPosition);
924-
}
925-
};
926-
this.onAfterEmitToken = node => {
927-
if (node) {
928-
setEnd(node, this.lastNonTriviaPosition);
929-
}
930-
};
931-
}
891+
interface TextChangesWriter extends EmitTextWriter, PrintHandlers {}
892+
893+
function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter {
894+
let lastNonTriviaPosition = 0;
895+
932896

933-
private setLastNonTriviaPosition(s: string, force: boolean) {
897+
const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine);
898+
const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => {
899+
if (node) {
900+
setPos(node, lastNonTriviaPosition);
901+
}
902+
printCallback(hint, node);
903+
if (node) {
904+
setEnd(node, lastNonTriviaPosition);
905+
}
906+
};
907+
const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => {
908+
if (nodes) {
909+
setPos(nodes, lastNonTriviaPosition);
910+
}
911+
};
912+
const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => {
913+
if (nodes) {
914+
setEnd(nodes, lastNonTriviaPosition);
915+
}
916+
};
917+
const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => {
918+
if (node) {
919+
setPos(node, lastNonTriviaPosition);
920+
}
921+
};
922+
const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => {
923+
if (node) {
924+
setEnd(node, lastNonTriviaPosition);
925+
}
926+
};
927+
928+
function setLastNonTriviaPosition(s: string, force: boolean) {
934929
if (force || !isTrivia(s)) {
935-
this.lastNonTriviaPosition = this.writer.getTextPos();
930+
lastNonTriviaPosition = writer.getTextPos();
936931
let i = 0;
937932
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
938933
i++;
939934
}
940935
// trim trailing whitespaces
941-
this.lastNonTriviaPosition -= i;
936+
lastNonTriviaPosition -= i;
942937
}
943938
}
944939

945-
write(s: string): void {
946-
this.writer.write(s);
947-
this.setLastNonTriviaPosition(s, /*force*/ false);
948-
}
949-
writeComment(s: string): void {
950-
this.writer.writeComment(s);
951-
}
952-
writeKeyword(s: string): void {
953-
this.writer.writeKeyword(s);
954-
this.setLastNonTriviaPosition(s, /*force*/ false);
955-
}
956-
writeOperator(s: string): void {
957-
this.writer.writeOperator(s);
958-
this.setLastNonTriviaPosition(s, /*force*/ false);
959-
}
960-
writePunctuation(s: string): void {
961-
this.writer.writePunctuation(s);
962-
this.setLastNonTriviaPosition(s, /*force*/ false);
963-
}
964-
writeTrailingSemicolon(s: string): void {
965-
this.writer.writeTrailingSemicolon(s);
966-
this.setLastNonTriviaPosition(s, /*force*/ false);
967-
}
968-
writeParameter(s: string): void {
969-
this.writer.writeParameter(s);
970-
this.setLastNonTriviaPosition(s, /*force*/ false);
971-
}
972-
writeProperty(s: string): void {
973-
this.writer.writeProperty(s);
974-
this.setLastNonTriviaPosition(s, /*force*/ false);
975-
}
976-
writeSpace(s: string): void {
977-
this.writer.writeSpace(s);
978-
this.setLastNonTriviaPosition(s, /*force*/ false);
979-
}
980-
writeStringLiteral(s: string): void {
981-
this.writer.writeStringLiteral(s);
982-
this.setLastNonTriviaPosition(s, /*force*/ false);
983-
}
984-
writeSymbol(s: string, sym: Symbol): void {
985-
this.writer.writeSymbol(s, sym);
986-
this.setLastNonTriviaPosition(s, /*force*/ false);
987-
}
988-
writeLine(): void {
989-
this.writer.writeLine();
990-
}
991-
increaseIndent(): void {
992-
this.writer.increaseIndent();
993-
}
994-
decreaseIndent(): void {
995-
this.writer.decreaseIndent();
996-
}
997-
getText(): string {
998-
return this.writer.getText();
999-
}
1000-
rawWrite(s: string): void {
1001-
this.writer.rawWrite(s);
1002-
this.setLastNonTriviaPosition(s, /*force*/ false);
1003-
}
1004-
writeLiteral(s: string): void {
1005-
this.writer.writeLiteral(s);
1006-
this.setLastNonTriviaPosition(s, /*force*/ true);
1007-
}
1008-
getTextPos(): number {
1009-
return this.writer.getTextPos();
1010-
}
1011-
getLine(): number {
1012-
return this.writer.getLine();
1013-
}
1014-
getColumn(): number {
1015-
return this.writer.getColumn();
1016-
}
1017-
getIndent(): number {
1018-
return this.writer.getIndent();
1019-
}
1020-
isAtStartOfLine(): boolean {
1021-
return this.writer.isAtStartOfLine();
1022-
}
1023-
clear(): void {
1024-
this.writer.clear();
1025-
this.lastNonTriviaPosition = 0;
940+
function write(s: string): void {
941+
writer.write(s);
942+
setLastNonTriviaPosition(s, /*force*/ false);
1026943
}
944+
function writeComment(s: string): void {
945+
writer.writeComment(s);
946+
}
947+
function writeKeyword(s: string): void {
948+
writer.writeKeyword(s);
949+
setLastNonTriviaPosition(s, /*force*/ false);
950+
}
951+
function writeOperator(s: string): void {
952+
writer.writeOperator(s);
953+
setLastNonTriviaPosition(s, /*force*/ false);
954+
}
955+
function writePunctuation(s: string): void {
956+
writer.writePunctuation(s);
957+
setLastNonTriviaPosition(s, /*force*/ false);
958+
}
959+
function writeTrailingSemicolon(s: string): void {
960+
writer.writeTrailingSemicolon(s);
961+
setLastNonTriviaPosition(s, /*force*/ false);
962+
}
963+
function writeParameter(s: string): void {
964+
writer.writeParameter(s);
965+
setLastNonTriviaPosition(s, /*force*/ false);
966+
}
967+
function writeProperty(s: string): void {
968+
writer.writeProperty(s);
969+
setLastNonTriviaPosition(s, /*force*/ false);
970+
}
971+
function writeSpace(s: string): void {
972+
writer.writeSpace(s);
973+
setLastNonTriviaPosition(s, /*force*/ false);
974+
}
975+
function writeStringLiteral(s: string): void {
976+
writer.writeStringLiteral(s);
977+
setLastNonTriviaPosition(s, /*force*/ false);
978+
}
979+
function writeSymbol(s: string, sym: Symbol): void {
980+
writer.writeSymbol(s, sym);
981+
setLastNonTriviaPosition(s, /*force*/ false);
982+
}
983+
function writeLine(): void {
984+
writer.writeLine();
985+
}
986+
function increaseIndent(): void {
987+
writer.increaseIndent();
988+
}
989+
function decreaseIndent(): void {
990+
writer.decreaseIndent();
991+
}
992+
function getText(): string {
993+
return writer.getText();
994+
}
995+
function rawWrite(s: string): void {
996+
writer.rawWrite(s);
997+
setLastNonTriviaPosition(s, /*force*/ false);
998+
}
999+
function writeLiteral(s: string): void {
1000+
writer.writeLiteral(s);
1001+
setLastNonTriviaPosition(s, /*force*/ true);
1002+
}
1003+
function getTextPos(): number {
1004+
return writer.getTextPos();
1005+
}
1006+
function getLine(): number {
1007+
return writer.getLine();
1008+
}
1009+
function getColumn(): number {
1010+
return writer.getColumn();
1011+
}
1012+
function getIndent(): number {
1013+
return writer.getIndent();
1014+
}
1015+
function isAtStartOfLine(): boolean {
1016+
return writer.isAtStartOfLine();
1017+
}
1018+
function clear(): void {
1019+
writer.clear();
1020+
lastNonTriviaPosition = 0;
1021+
}
1022+
1023+
return {
1024+
onEmitNode,
1025+
onBeforeEmitNodeArray,
1026+
onAfterEmitNodeArray,
1027+
onBeforeEmitToken,
1028+
onAfterEmitToken,
1029+
write,
1030+
writeComment,
1031+
writeKeyword,
1032+
writeOperator,
1033+
writePunctuation,
1034+
writeTrailingSemicolon,
1035+
writeParameter,
1036+
writeProperty,
1037+
writeSpace,
1038+
writeStringLiteral,
1039+
writeSymbol,
1040+
writeLine,
1041+
increaseIndent,
1042+
decreaseIndent,
1043+
getText,
1044+
rawWrite,
1045+
writeLiteral,
1046+
getTextPos,
1047+
getLine,
1048+
getColumn,
1049+
getIndent,
1050+
isAtStartOfLine,
1051+
clear
1052+
};
10271053
}
10281054

10291055
function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number {

src/services/utilities.ts

+47
Original file line numberDiff line numberDiff line change
@@ -1991,4 +1991,51 @@ namespace ts {
19911991
});
19921992
return typeIsAccessible ? res : undefined;
19931993
}
1994+
1995+
export function syntaxUsuallyHasTrailingSemicolon(kind: SyntaxKind) {
1996+
return kind === SyntaxKind.VariableStatement
1997+
|| kind === SyntaxKind.ExpressionStatement
1998+
|| kind === SyntaxKind.DoStatement
1999+
|| kind === SyntaxKind.ContinueStatement
2000+
|| kind === SyntaxKind.BreakStatement
2001+
|| kind === SyntaxKind.ReturnStatement
2002+
|| kind === SyntaxKind.ThrowStatement
2003+
|| kind === SyntaxKind.DebuggerStatement
2004+
|| kind === SyntaxKind.PropertyDeclaration
2005+
|| kind === SyntaxKind.TypeAliasDeclaration
2006+
|| kind === SyntaxKind.ImportDeclaration
2007+
|| kind === SyntaxKind.ImportEqualsDeclaration
2008+
|| kind === SyntaxKind.ExportDeclaration;
2009+
}
2010+
2011+
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {
2012+
let withSemicolon = 0;
2013+
let withoutSemicolon = 0;
2014+
const nStatementsToObserve = 5;
2015+
forEachChild(sourceFile, function visit(node): boolean | undefined {
2016+
if (syntaxUsuallyHasTrailingSemicolon(node.kind)) {
2017+
const lastToken = node.getLastToken(sourceFile);
2018+
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
2019+
withSemicolon++;
2020+
}
2021+
else {
2022+
withoutSemicolon++;
2023+
}
2024+
}
2025+
if (withSemicolon + withoutSemicolon >= nStatementsToObserve) {
2026+
return true;
2027+
}
2028+
2029+
return forEachChild(node, visit);
2030+
});
2031+
2032+
// One statement missing a semicolon isn’t sufficient evidence to say the user
2033+
// doesn’t want semicolons, because they may not even be done writing that statement.
2034+
if (withSemicolon === 0 && withoutSemicolon <= 1) {
2035+
return true;
2036+
}
2037+
2038+
// If even 2/5 places have a semicolon, the user probably wants semicolons
2039+
return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve;
2040+
}
19942041
}

0 commit comments

Comments
 (0)