Skip to content

Commit 878bf80

Browse files
author
Andy
authored
Make GetEditsForFileRenameRequestArgs not extend FileRequestArgs (#25052)
* Make GetEditsForFileRenameRequestArgs not extend FileRequestArgs * Code review: check new location first, and use scriptInfo.getDefaultProject() * Remove changes to e getDefaultProjectForFile (now #25060) * Undo API changes (#24966) * Combine edits from all projects (fixes #25052) * Update API (#24966) * Ignore orphan projects or projects with language service disabled
1 parent 806a661 commit 878bf80

File tree

5 files changed

+118
-15
lines changed

5 files changed

+118
-15
lines changed

src/server/editorServices.ts

+15
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,13 @@ namespace ts.server {
692692
return this.findExternalProjectByProjectName(projectName) || this.findConfiguredProjectByProjectName(toNormalizedPath(projectName));
693693
}
694694

695+
/* @internal */
696+
forEachProject(cb: (project: Project) => void) {
697+
for (const p of this.inferredProjects) cb(p);
698+
this.configuredProjects.forEach(cb);
699+
this.externalProjects.forEach(cb);
700+
}
701+
695702
getDefaultProjectForFile(fileName: NormalizedPath, ensureProject: boolean): Project | undefined {
696703
return ensureProject ? this.ensureDefaultProjectForFile(fileName) : this.tryGetDefaultProjectForFile(fileName);
697704
}
@@ -750,6 +757,14 @@ namespace ts.server {
750757
return info && info.getPreferences() || this.hostConfiguration.preferences;
751758
}
752759

760+
getHostFormatCodeOptions(): FormatCodeSettings {
761+
return this.hostConfiguration.formatCodeOptions;
762+
}
763+
764+
getHostPreferences(): UserPreferences {
765+
return this.hostConfiguration.preferences;
766+
}
767+
753768
private onSourceFileChanged(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
754769
const info = this.getScriptInfoForPath(path);
755770
if (!info) {

src/server/protocol.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -627,9 +627,8 @@ namespace ts.server.protocol {
627627
arguments: GetEditsForFileRenameRequestArgs;
628628
}
629629

630-
// Note: The file from FileRequestArgs is just any file in the project.
631-
// We will generate code changes for every file in that project, so the choice is arbitrary.
632-
export interface GetEditsForFileRenameRequestArgs extends FileRequestArgs {
630+
/** Note: Paths may also be directories. */
631+
export interface GetEditsForFileRenameRequestArgs {
633632
readonly oldFilePath: string;
634633
readonly newFilePath: string;
635634
}

src/server/session.ts

+37-8
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ namespace ts.server {
11421142
return this.getPosition(args, scriptInfo);
11431143
}
11441144

1145-
private getFileAndProject(args: protocol.FileRequestArgs): { file: NormalizedPath, project: Project } {
1145+
private getFileAndProject(args: protocol.FileRequestArgs): FileAndProject {
11461146
return this.getFileAndProjectWorker(args.file, args.projectFileName);
11471147
}
11481148

@@ -1738,9 +1738,23 @@ namespace ts.server {
17381738
}
17391739

17401740
private getEditsForFileRename(args: protocol.GetEditsForFileRenameRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.FileCodeEdits> | ReadonlyArray<FileTextChanges> {
1741-
const { file, project } = this.getFileAndProject(args);
1742-
const changes = project.getLanguageService().getEditsForFileRename(toNormalizedPath(args.oldFilePath), toNormalizedPath(args.newFilePath), this.getFormatOptions(file), this.getPreferences(file));
1743-
return simplifiedResult ? this.mapTextChangesToCodeEdits(project, changes) : changes;
1741+
const oldPath = toNormalizedPath(args.oldFilePath);
1742+
const newPath = toNormalizedPath(args.newFilePath);
1743+
const formatOptions = this.getHostFormatOptions();
1744+
const preferences = this.getHostPreferences();
1745+
1746+
const changes: (protocol.FileCodeEdits | FileTextChanges)[] = [];
1747+
this.projectService.forEachProject(project => {
1748+
if (project.isOrphan() || !project.languageServiceEnabled) return;
1749+
for (const fileTextChanges of project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences)) {
1750+
// Subsequent projects may make conflicting edits to the same file -- just go with the first.
1751+
if (!changes.some(f => f.fileName === fileTextChanges.fileName)) {
1752+
changes.push(simplifiedResult ? this.mapTextChangeToCodeEdit(project, fileTextChanges) : fileTextChanges);
1753+
}
1754+
}
1755+
});
1756+
1757+
return changes as ReadonlyArray<protocol.FileCodeEdits> | ReadonlyArray<FileTextChanges>;
17441758
}
17451759

17461760
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CodeFixAction> | ReadonlyArray<CodeFixAction> | undefined {
@@ -1810,10 +1824,12 @@ namespace ts.server {
18101824
}
18111825

18121826
private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray<FileTextChanges>): protocol.FileCodeEdits[] {
1813-
return textChanges.map(change => {
1814-
const path = normalizedPathToPath(toNormalizedPath(change.fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName));
1815-
return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(path));
1816-
});
1827+
return textChanges.map(change => this.mapTextChangeToCodeEdit(project, change));
1828+
}
1829+
1830+
private mapTextChangeToCodeEdit(project: Project, change: FileTextChanges): protocol.FileCodeEdits {
1831+
const path = normalizedPathToPath(toNormalizedPath(change.fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName));
1832+
return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(path));
18171833
}
18181834

18191835
private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit {
@@ -2303,6 +2319,19 @@ namespace ts.server {
23032319
private getPreferences(file: NormalizedPath): UserPreferences {
23042320
return this.projectService.getPreferences(file);
23052321
}
2322+
2323+
private getHostFormatOptions(): FormatCodeSettings {
2324+
return this.projectService.getHostFormatCodeOptions();
2325+
}
2326+
2327+
private getHostPreferences(): UserPreferences {
2328+
return this.projectService.getHostPreferences();
2329+
}
2330+
}
2331+
2332+
interface FileAndProject {
2333+
readonly file: NormalizedPath;
2334+
readonly project: Project;
23062335
}
23072336

23082337
function mapTextChangesToCodeEdits(textChanges: FileTextChanges, sourceFile: SourceFile | undefined): protocol.FileCodeEdits {

src/testRunner/unittests/tsserverProjectSystem.ts

+57-3
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,18 @@ namespace ts.projectSystem {
421421
return createTextSpan(start, substring.length);
422422
}
423423

424+
function protocolTextSpanFromSubstring(str: string, substring: string): protocol.TextSpan {
425+
const start = str.indexOf(substring);
426+
Debug.assert(start !== -1);
427+
const lineStarts = computeLineStarts(str);
428+
const toLocation = (pos: number) => lineAndCharacterToLocation(computeLineAndCharacterOfPosition(lineStarts, pos));
429+
return { start: toLocation(start), end: toLocation(start + substring.length) };
430+
}
431+
432+
function lineAndCharacterToLocation(lc: LineAndCharacter): protocol.Location {
433+
return { line: lc.line + 1, offset: lc.character + 1 };
434+
}
435+
424436
/**
425437
* Test server cancellation token used to mock host token cancellation requests.
426438
* The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls
@@ -464,14 +476,13 @@ namespace ts.projectSystem {
464476
}
465477
}
466478

467-
export function makeSessionRequest<T>(command: string, args: T) {
468-
const newRequest: protocol.Request = {
479+
export function makeSessionRequest<T>(command: string, args: T): protocol.Request {
480+
return {
469481
seq: 0,
470482
type: "request",
471483
command,
472484
arguments: args
473485
};
474-
return newRequest;
475486
}
476487

477488
export function openFilesForSession(files: ReadonlyArray<File>, session: server.Session) {
@@ -8682,6 +8693,49 @@ export const x = 10;`
86828693
}],
86838694
}]);
86848695
});
8696+
8697+
it("works with multiple projects", () => {
8698+
const aUserTs: File = {
8699+
path: "/a/user.ts",
8700+
content: 'import { x } from "./old";',
8701+
};
8702+
const aOldTs: File = {
8703+
path: "/a/old.ts",
8704+
content: "export const x = 0;",
8705+
};
8706+
const aTsconfig: File = {
8707+
path: "/a/tsconfig.json",
8708+
content: "{}",
8709+
};
8710+
const bUserTs: File = {
8711+
path: "/b/user.ts",
8712+
content: 'import { x } from "../a/old";',
8713+
};
8714+
const bTsconfig: File = {
8715+
path: "/b/tsconfig.json",
8716+
content: "{}",
8717+
};
8718+
8719+
const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]);
8720+
const session = createSession(host);
8721+
openFilesForSession([aUserTs, bUserTs], session);
8722+
8723+
const renameRequest = makeSessionRequest<protocol.GetEditsForFileRenameRequestArgs>(CommandNames.GetEditsForFileRename, {
8724+
oldFilePath: "/a/old.ts",
8725+
newFilePath: "/a/new.ts",
8726+
});
8727+
const response = session.executeCommand(renameRequest).response as protocol.GetEditsForFileRenameResponse["body"];
8728+
assert.deepEqual(response, [
8729+
{
8730+
fileName: aUserTs.path,
8731+
textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }],
8732+
},
8733+
{
8734+
fileName: bUserTs.path,
8735+
textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }],
8736+
},
8737+
]);
8738+
});
86858739
});
86868740

86878741
describe("tsserverProjectSystem document registry in project service", () => {

tests/baselines/reference/api/tsserverlibrary.d.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -12464,7 +12464,7 @@ declare namespace ts.server.protocol {
1246412464
command: CommandTypes.GetEditsForFileRename;
1246512465
arguments: GetEditsForFileRenameRequestArgs;
1246612466
}
12467-
interface GetEditsForFileRenameRequestArgs extends FileRequestArgs {
12467+
interface GetEditsForFileRenameRequestArgs {
1246812468
readonly oldFilePath: string;
1246912469
readonly newFilePath: string;
1247012470
}
@@ -13836,6 +13836,7 @@ declare namespace ts.server {
1383613836
private delayUpdateProjectGraphs;
1383713837
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void;
1383813838
findProject(projectName: string): Project | undefined;
13839+
forEachProject(cb: (project: Project) => void): void;
1383913840
getDefaultProjectForFile(fileName: NormalizedPath, ensureProject: boolean): Project | undefined;
1384013841
tryGetDefaultProjectForFile(fileName: NormalizedPath): Project | undefined;
1384113842
ensureDefaultProjectForFile(fileName: NormalizedPath): Project;
@@ -13844,6 +13845,8 @@ declare namespace ts.server {
1384413845
private ensureProjectStructuresUptoDate;
1384513846
getFormatCodeOptions(file: NormalizedPath): FormatCodeSettings;
1384613847
getPreferences(file: NormalizedPath): UserPreferences;
13848+
getHostFormatCodeOptions(): FormatCodeSettings;
13849+
getHostPreferences(): UserPreferences;
1384713850
private onSourceFileChanged;
1384813851
private handleDeletedFile;
1384913852
watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, project: ConfiguredProject): FileWatcher;
@@ -14074,6 +14077,7 @@ declare namespace ts.server {
1407414077
private mapCodeAction;
1407514078
private mapCodeFixAction;
1407614079
private mapTextChangesToCodeEdits;
14080+
private mapTextChangeToCodeEdit;
1407714081
private convertTextChangeToCodeEdit;
1407814082
private getBraceMatching;
1407914083
private getDiagnosticsForProject;
@@ -14090,6 +14094,8 @@ declare namespace ts.server {
1409014094
onMessage(message: string): void;
1409114095
private getFormatOptions;
1409214096
private getPreferences;
14097+
private getHostFormatOptions;
14098+
private getHostPreferences;
1409314099
}
1409414100
interface HandlerResponse {
1409514101
response?: {};

0 commit comments

Comments
 (0)