Skip to content

Commit 7d9b22e

Browse files
authored
Add semicolon preference to formatter options (#33402)
* Add UserPreferences for semicolons * Prototype formatter semicolon removal * Implement semicolon insertion * Fix existing tests * Start adding tests * Fix some edge cases of semicolon deletion * Fix semicolon removal before comments * Fix indentation * Test on checker * Replace semicolon-omitting writer with formatter preference * Fix writing new nodes, update protocol * Rename option * Really fix formatting synthetic nodes * Fix refactoring misses * Un-update submodules gahhhh * Update APIs * Update for ESLint * Revert accidental test change * De-kludge deduplication of EOF processing * Omit last element semicolon from single-line object-like types * Revert "Omit last element semicolon from single-line object-like types" This reverts commit 5625cb0. * Fix straggler test * Add test for leading semicolon class members * Rename a lot of stuff for clarity * Invert some boolean logic
1 parent 3dd7b84 commit 7d9b22e

29 files changed

+817
-257
lines changed

src/compiler/core.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,21 @@ namespace ts {
329329
return undefined;
330330
}
331331

332+
/**
333+
* Like `forEach`, but iterates in reverse order.
334+
*/
335+
export function forEachRight<T, U>(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
336+
if (array) {
337+
for (let i = array.length - 1; i >= 0; i--) {
338+
const result = callback(array[i], i);
339+
if (result) {
340+
return result;
341+
}
342+
}
343+
}
344+
return undefined;
345+
}
346+
332347
/** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */
333348
export function firstDefined<T, U>(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
334349
if (array === undefined) {
@@ -2159,8 +2174,19 @@ namespace ts {
21592174
return (arg: T) => f(arg) && g(arg);
21602175
}
21612176

2162-
export function or<T>(f: (arg: T) => boolean, g: (arg: T) => boolean): (arg: T) => boolean {
2163-
return arg => f(arg) || g(arg);
2177+
export function or<T extends unknown>(...fs: ((arg: T) => boolean)[]): (arg: T) => boolean {
2178+
return arg => {
2179+
for (const f of fs) {
2180+
if (f(arg)) {
2181+
return true;
2182+
}
2183+
}
2184+
return false;
2185+
};
2186+
}
2187+
2188+
export function not<T extends unknown[]>(fn: (...args: T) => boolean): (...args: T) => boolean {
2189+
return (...args) => !fn(...args);
21642190
}
21652191

21662192
export function assertType<T>(_: T): void { }

src/compiler/utilities.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3380,11 +3380,7 @@ namespace ts {
33803380
};
33813381
}
33823382

3383-
export interface TrailingSemicolonDeferringWriter extends EmitTextWriter {
3384-
resetPendingTrailingSemicolon(): void;
3385-
}
3386-
3387-
export function getTrailingSemicolonDeferringWriter(writer: EmitTextWriter): TrailingSemicolonDeferringWriter {
3383+
export function getTrailingSemicolonDeferringWriter(writer: EmitTextWriter): EmitTextWriter {
33883384
let pendingTrailingSemicolon = false;
33893385

33903386
function commitPendingTrailingSemicolon() {
@@ -3451,20 +3447,6 @@ namespace ts {
34513447
commitPendingTrailingSemicolon();
34523448
writer.decreaseIndent();
34533449
},
3454-
resetPendingTrailingSemicolon() {
3455-
pendingTrailingSemicolon = false;
3456-
}
3457-
};
3458-
}
3459-
3460-
export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter {
3461-
const deferringWriter = getTrailingSemicolonDeferringWriter(writer);
3462-
return {
3463-
...deferringWriter,
3464-
writeLine() {
3465-
deferringWriter.resetPendingTrailingSemicolon();
3466-
writer.writeLine();
3467-
},
34683450
};
34693451
}
34703452

src/harness/fourslash.ts

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,7 +1683,7 @@ namespace FourSlash {
16831683
if (this.enableFormatting) {
16841684
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeSettings);
16851685
if (edits.length) {
1686-
offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
1686+
offset += this.applyEdits(this.activeFile.fileName, edits);
16871687
}
16881688
}
16891689
}
@@ -1756,7 +1756,7 @@ namespace FourSlash {
17561756
if (this.enableFormatting) {
17571757
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeSettings);
17581758
if (edits.length) {
1759-
offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
1759+
offset += this.applyEdits(this.activeFile.fileName, edits);
17601760
}
17611761
}
17621762
}
@@ -1775,7 +1775,7 @@ namespace FourSlash {
17751775
if (this.enableFormatting) {
17761776
const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeSettings);
17771777
if (edits.length) {
1778-
this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
1778+
this.applyEdits(this.activeFile.fileName, edits);
17791779
}
17801780
}
17811781

@@ -1810,9 +1810,7 @@ namespace FourSlash {
18101810
* @returns The number of characters added to the file as a result of the edits.
18111811
* May be negative.
18121812
*/
1813-
private applyEdits(fileName: string, edits: readonly ts.TextChange[], isFormattingEdit: boolean): number {
1814-
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
1815-
const oldContent = this.getFileContent(fileName);
1813+
private applyEdits(fileName: string, edits: readonly ts.TextChange[]): number {
18161814
let runningOffset = 0;
18171815

18181816
forEachTextChange(edits, edit => {
@@ -1833,14 +1831,6 @@ namespace FourSlash {
18331831
runningOffset += editDelta;
18341832
});
18351833

1836-
if (isFormattingEdit) {
1837-
const newContent = this.getFileContent(fileName);
1838-
1839-
if (this.removeWhitespace(newContent) !== this.removeWhitespace(oldContent)) {
1840-
this.raiseError("Formatting operation destroyed non-whitespace content");
1841-
}
1842-
}
1843-
18441834
return runningOffset;
18451835
}
18461836

@@ -1856,17 +1846,17 @@ namespace FourSlash {
18561846

18571847
public formatDocument() {
18581848
const edits = this.languageService.getFormattingEditsForDocument(this.activeFile.fileName, this.formatCodeSettings);
1859-
this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
1849+
this.applyEdits(this.activeFile.fileName, edits);
18601850
}
18611851

18621852
public formatSelection(start: number, end: number) {
18631853
const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, end, this.formatCodeSettings);
1864-
this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
1854+
this.applyEdits(this.activeFile.fileName, edits);
18651855
}
18661856

18671857
public formatOnType(pos: number, key: string) {
18681858
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, pos, key, this.formatCodeSettings);
1869-
this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
1859+
this.applyEdits(this.activeFile.fileName, edits);
18701860
}
18711861

18721862
private editScriptAndUpdateMarkers(fileName: string, editStart: number, editEnd: number, newText: string) {
@@ -2414,7 +2404,7 @@ namespace FourSlash {
24142404

24152405
if (options.applyChanges) {
24162406
for (const change of action.changes) {
2417-
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
2407+
this.applyEdits(change.fileName, change.textChanges);
24182408
}
24192409
this.verifyNewContentAfterChange(options, action.changes.map(c => c.fileName));
24202410
}
@@ -2497,7 +2487,7 @@ namespace FourSlash {
24972487

24982488
private applyChanges(changes: readonly ts.FileTextChanges[]): void {
24992489
for (const change of changes) {
2500-
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
2490+
this.applyEdits(change.fileName, change.textChanges);
25012491
}
25022492
}
25032493

@@ -2525,7 +2515,7 @@ namespace FourSlash {
25252515
ts.Debug.assert(codeFix.changes.length === 1);
25262516
const change = ts.first(codeFix.changes);
25272517
ts.Debug.assert(change.fileName === fileName);
2528-
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
2518+
this.applyEdits(change.fileName, change.textChanges);
25292519
const text = range ? this.rangeText(range) : this.getFileContent(this.activeFile.fileName);
25302520
actualTextArray.push(text);
25312521
scriptInfo.updateContent(originalContent);
@@ -2929,7 +2919,7 @@ namespace FourSlash {
29292919

29302920
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName, ts.emptyOptions)!;
29312921
for (const edit of editInfo.edits) {
2932-
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
2922+
this.applyEdits(edit.fileName, edit.textChanges);
29332923
}
29342924

29352925
let renameFilename: string | undefined;
@@ -3045,7 +3035,7 @@ namespace FourSlash {
30453035
const editInfo = this.languageService.getEditsForRefactor(marker.fileName, formattingOptions, marker.position, refactorNameToApply, actionName, ts.emptyOptions)!;
30463036

30473037
for (const edit of editInfo.edits) {
3048-
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
3038+
this.applyEdits(edit.fileName, edit.textChanges);
30493039
}
30503040
const actualContent = this.getFileContent(marker.fileName);
30513041

src/server/protocol.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2956,6 +2956,12 @@ namespace ts.server.protocol {
29562956
Smart = "Smart",
29572957
}
29582958

2959+
export enum SemicolonPreference {
2960+
Ignore = "ignore",
2961+
Insert = "insert",
2962+
Remove = "remove",
2963+
}
2964+
29592965
export interface EditorSettings {
29602966
baseIndentSize?: number;
29612967
indentSize?: number;
@@ -2982,6 +2988,7 @@ namespace ts.server.protocol {
29822988
placeOpenBraceOnNewLineForFunctions?: boolean;
29832989
placeOpenBraceOnNewLineForControlBlocks?: boolean;
29842990
insertSpaceBeforeTypeAnnotation?: boolean;
2991+
semicolons?: SemicolonPreference;
29852992
}
29862993

29872994
export interface UserPreferences {

src/services/codefixes/importFixes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ namespace ts.codefix {
169169
// We sort the best codefixes first, so taking `first` is best for completions.
170170
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier;
171171
const fix = first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences));
172-
return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
172+
return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
173173
}
174174

175175
function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {

0 commit comments

Comments
 (0)