Skip to content

Commit 0a696c9

Browse files
committed
Ensure formatter can always get a newline character
1 parent c1f676d commit 0a696c9

File tree

12 files changed

+59
-21
lines changed

12 files changed

+59
-21
lines changed

src/harness/fourslashImpl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,12 @@ namespace FourSlash {
515515
}
516516
}
517517

518+
public verifyOrganizeImports(newContent: string) {
519+
const changes = this.languageService.organizeImports({ fileName: this.activeFile.fileName, type: "file" }, this.formatCodeSettings, ts.emptyOptions);
520+
this.applyChanges(changes);
521+
this.verifyFileContent(this.activeFile.fileName, newContent);
522+
}
523+
518524
private raiseError(message: string): never {
519525
throw new Error(this.messageAtLastKnownMarker(message));
520526
}

src/harness/fourslashInterfaceImpl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,10 @@ namespace FourSlashInterface {
560560
public noMoveToNewFile(): void {
561561
this.state.noMoveToNewFile();
562562
}
563+
564+
public organizeImports(newContent: string) {
565+
this.state.verifyOrganizeImports(newContent);
566+
}
563567
}
564568

565569
export class Edit {

src/services/formatting/formatting.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace ts.formatting {
33
export interface FormatContext {
44
readonly options: FormatCodeSettings;
55
readonly getRules: RulesMap;
6+
readonly host: FormattingHost;
67
}
78

89
export interface TextRangeWithKind<T extends SyntaxKind = SyntaxKind> extends TextRange {
@@ -394,7 +395,7 @@ namespace ts.formatting {
394395
initialIndentation: number,
395396
delta: number,
396397
formattingScanner: FormattingScanner,
397-
{ options, getRules }: FormatContext,
398+
{ options, getRules, host }: FormatContext,
398399
requestKind: FormattingRequestKind,
399400
rangeContainsError: (r: TextRange) => boolean,
400401
sourceFile: SourceFileLike): TextChange[] {
@@ -1193,7 +1194,7 @@ namespace ts.formatting {
11931194
previousRange: TextRangeWithKind,
11941195
previousStartLine: number,
11951196
currentRange: TextRangeWithKind,
1196-
currentStartLine: number,
1197+
currentStartLine: number
11971198
): LineAction {
11981199
const onLaterLine = currentStartLine !== previousStartLine;
11991200
switch (rule.action) {
@@ -1221,7 +1222,7 @@ namespace ts.formatting {
12211222
// edit should not be applied if we have one line feed between elements
12221223
const lineDelta = currentStartLine - previousStartLine;
12231224
if (lineDelta !== 1) {
1224-
recordReplace(previousRange.end, currentRange.pos - previousRange.end, options.newLineCharacter!);
1225+
recordReplace(previousRange.end, currentRange.pos - previousRange.end, getNewLineOrDefaultFromHost(host, options));
12251226
return onLaterLine ? LineAction.None : LineAction.LineAdded;
12261227
}
12271228
break;

src/services/formatting/rulesMap.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @internal */
22
namespace ts.formatting {
3-
export function getFormatContext(options: FormatCodeSettings): FormatContext {
4-
return { options, getRules: getRulesMap() };
3+
export function getFormatContext(options: FormatCodeSettings, host: FormattingHost): FormatContext {
4+
return { options, getRules: getRulesMap(), host };
55
}
66

77
let rulesMapCache: RulesMap | undefined;

src/services/services.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ namespace ts {
15021502
position,
15031503
{ name, source },
15041504
host,
1505-
(formattingOptions && formatting.getFormatContext(formattingOptions))!, // TODO: GH#18217
1505+
(formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217
15061506
preferences,
15071507
cancellationToken,
15081508
);
@@ -1840,16 +1840,16 @@ namespace ts {
18401840

18411841
function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
18421842
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
1843-
return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options)));
1843+
return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host));
18441844
}
18451845

18461846
function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
1847-
return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options)));
1847+
return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host));
18481848
}
18491849

18501850
function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
18511851
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
1852-
const formatContext = formatting.getFormatContext(toEditorSettings(options));
1852+
const formatContext = formatting.getFormatContext(toEditorSettings(options), host);
18531853

18541854
if (!isInComment(sourceFile, position)) {
18551855
switch (key) {
@@ -1871,7 +1871,7 @@ namespace ts {
18711871
synchronizeHostData();
18721872
const sourceFile = getValidSourceFile(fileName);
18731873
const span = createTextSpanFromBounds(start, end);
1874-
const formatContext = formatting.getFormatContext(formatOptions);
1874+
const formatContext = formatting.getFormatContext(formatOptions, host);
18751875

18761876
return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => {
18771877
cancellationToken.throwIfCancellationRequested();
@@ -1883,7 +1883,7 @@ namespace ts {
18831883
synchronizeHostData();
18841884
Debug.assert(scope.type === "file");
18851885
const sourceFile = getValidSourceFile(scope.fileName);
1886-
const formatContext = formatting.getFormatContext(formatOptions);
1886+
const formatContext = formatting.getFormatContext(formatOptions, host);
18871887

18881888
return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences });
18891889
}
@@ -1892,13 +1892,13 @@ namespace ts {
18921892
synchronizeHostData();
18931893
Debug.assert(scope.type === "file");
18941894
const sourceFile = getValidSourceFile(scope.fileName);
1895-
const formatContext = formatting.getFormatContext(formatOptions);
1895+
const formatContext = formatting.getFormatContext(formatOptions, host);
18961896

18971897
return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences);
18981898
}
18991899

19001900
function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] {
1901-
return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences, sourceMapper);
1901+
return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper);
19021902
}
19031903

19041904
function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult>;
@@ -2141,7 +2141,7 @@ namespace ts {
21412141
endPosition,
21422142
program: getProgram()!,
21432143
host,
2144-
formatContext: formatting.getFormatContext(formatOptions!), // TODO: GH#18217
2144+
formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217
21452145
cancellationToken,
21462146
preferences,
21472147
};

src/services/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,15 @@ namespace ts {
203203
has(dependencyName: string, inGroups?: PackageJsonDependencyGroup): boolean;
204204
}
205205

206+
export interface FormattingHost {
207+
getNewLine?(): string;
208+
}
209+
206210
//
207211
// Public interface of the host of a language service instance.
208212
//
209-
export interface LanguageServiceHost extends GetEffectiveTypeRootsHost {
213+
export interface LanguageServiceHost extends GetEffectiveTypeRootsHost, FormattingHost {
210214
getCompilationSettings(): CompilerOptions;
211-
getNewLine?(): string;
212215
getProjectVersion?(): string;
213216
getScriptFileNames(): string[];
214217
getScriptKind?(fileName: string): ScriptKind;

src/services/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2085,7 +2085,7 @@ namespace ts {
20852085
/**
20862086
* The default is CRLF.
20872087
*/
2088-
export function getNewLineOrDefaultFromHost(host: LanguageServiceHost | LanguageServiceShimHost, formatSettings?: FormatCodeSettings) {
2088+
export function getNewLineOrDefaultFromHost(host: FormattingHost, formatSettings?: FormatCodeSettings) {
20892089
return (formatSettings && formatSettings.newLineCharacter) ||
20902090
(host.getNewLine && host.getNewLine()) ||
20912091
carriageReturnLineFeed;

src/testRunner/unittests/services/convertToAsyncFunction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ interface Array<T> {}`
306306
cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse },
307307
preferences: emptyOptions,
308308
host: notImplementedHost,
309-
formatContext: formatting.getFormatContext(testFormatSettings)
309+
formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost)
310310
};
311311

312312
const diagnostics = languageService.getSuggestionDiagnostics(f.path);

src/testRunner/unittests/services/extract/helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ namespace ts {
102102
startPosition: selectionRange.pos,
103103
endPosition: selectionRange.end,
104104
host: notImplementedHost,
105-
formatContext: formatting.getFormatContext(testFormatSettings),
105+
formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost),
106106
preferences: emptyOptions,
107107
};
108108
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));
@@ -164,7 +164,7 @@ namespace ts {
164164
startPosition: selectionRange.pos,
165165
endPosition: selectionRange.end,
166166
host: notImplementedHost,
167-
formatContext: formatting.getFormatContext(testFormatSettings),
167+
formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost),
168168
preferences: emptyOptions,
169169
};
170170
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));

src/testRunner/unittests/services/textChanges.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace ts {
1919
const newLineCharacter = getNewLineCharacter(printerOptions);
2020

2121
function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): formatting.FormatContext {
22-
return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : testFormatSettings);
22+
return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : testFormatSettings, notImplementedHost);
2323
}
2424

2525
// validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed.

tests/cases/fourslash/fourslash.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,8 @@ declare namespace FourSlashInterface {
394394
noMoveToNewFile(): void;
395395

396396
generateTypes(...options: GenerateTypesOptions[]): void;
397+
398+
organizeImports(newContent: string): void;
397399
}
398400
class edit {
399401
backspace(count?: number): void;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// #38548
4+
5+
////import {
6+
//// stat,
7+
//// statSync,
8+
////} from "fs";
9+
////export function fakeFn() {
10+
//// stat;
11+
//// statSync;
12+
////}
13+
14+
format.setFormatOptions({});
15+
16+
// Default newline is carriage return.
17+
// See `getNewLineOrDefaultFromHost()` in services/utilities.ts.
18+
verify.organizeImports(
19+
`import {\r\nstat,\r\nstatSync\r\n} from "fs";\r\nexport function fakeFn() {
20+
stat;
21+
statSync;
22+
}`);

0 commit comments

Comments
 (0)