Skip to content

Commit b37875f

Browse files
committed
Detect semicolons in file before writing quick fixes
1 parent 7815778 commit b37875f

File tree

2 files changed

+201
-128
lines changed

2 files changed

+201
-128
lines changed

src/services/textChanges.ts

Lines changed: 154 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,10 @@ namespace ts.textChanges {
834834

835835
/** Note: output node may be mutated input node. */
836836
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
837-
const writer = new Writer(newLineCharacter);
837+
const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile);
838+
const writer = createWriter(newLineCharacter, omitTrailingSemicolon);
838839
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
839-
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
840+
createPrinter({ newLine, neverAsciiEscape: true, omitTrailingSemicolon }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
840841
return { text: writer.getText(), node: assignPositionsToNode(node) };
841842
}
842843
}
@@ -874,143 +875,168 @@ namespace ts.textChanges {
874875
return nodeArray;
875876
}
876877

877-
class Writer implements EmitTextWriter, PrintHandlers {
878-
private lastNonTriviaPosition = 0;
879-
private readonly writer: EmitTextWriter;
880-
881-
public readonly onEmitNode: PrintHandlers["onEmitNode"];
882-
public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"];
883-
public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"];
884-
public readonly onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"];
885-
public readonly onAfterEmitToken: PrintHandlers["onAfterEmitToken"];
886-
887-
constructor(newLine: string) {
888-
this.writer = createTextWriter(newLine);
889-
this.onEmitNode = (hint, node, printCallback) => {
890-
if (node) {
891-
setPos(node, this.lastNonTriviaPosition);
892-
}
893-
printCallback(hint, node);
894-
if (node) {
895-
setEnd(node, this.lastNonTriviaPosition);
896-
}
897-
};
898-
this.onBeforeEmitNodeArray = nodes => {
899-
if (nodes) {
900-
setPos(nodes, this.lastNonTriviaPosition);
901-
}
902-
};
903-
this.onAfterEmitNodeArray = nodes => {
904-
if (nodes) {
905-
setEnd(nodes, this.lastNonTriviaPosition);
906-
}
907-
};
908-
this.onBeforeEmitToken = node => {
909-
if (node) {
910-
setPos(node, this.lastNonTriviaPosition);
911-
}
912-
};
913-
this.onAfterEmitToken = node => {
914-
if (node) {
915-
setEnd(node, this.lastNonTriviaPosition);
916-
}
917-
};
918-
}
878+
interface TextChangesWriter extends EmitTextWriter, PrintHandlers {}
879+
880+
function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter {
881+
let lastNonTriviaPosition = 0;
882+
919883

920-
private setLastNonTriviaPosition(s: string, force: boolean) {
884+
const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine);
885+
const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => {
886+
if (node) {
887+
setPos(node, lastNonTriviaPosition);
888+
}
889+
printCallback(hint, node);
890+
if (node) {
891+
setEnd(node, lastNonTriviaPosition);
892+
}
893+
};
894+
const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => {
895+
if (nodes) {
896+
setPos(nodes, lastNonTriviaPosition);
897+
}
898+
};
899+
const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => {
900+
if (nodes) {
901+
setEnd(nodes, lastNonTriviaPosition);
902+
}
903+
};
904+
const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => {
905+
if (node) {
906+
setPos(node, lastNonTriviaPosition);
907+
}
908+
};
909+
const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => {
910+
if (node) {
911+
setEnd(node, lastNonTriviaPosition);
912+
}
913+
};
914+
915+
function setLastNonTriviaPosition(s: string, force: boolean) {
921916
if (force || !isTrivia(s)) {
922-
this.lastNonTriviaPosition = this.writer.getTextPos();
917+
lastNonTriviaPosition = writer.getTextPos();
923918
let i = 0;
924919
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
925920
i++;
926921
}
927922
// trim trailing whitespaces
928-
this.lastNonTriviaPosition -= i;
923+
lastNonTriviaPosition -= i;
929924
}
930925
}
931926

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

10161042
function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number {

src/services/utilities.ts

Lines changed: 47 additions & 0 deletions
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)