Skip to content

Commit af412e3

Browse files
author
Andy
authored
mapTextChangesToCodeEditsUsingScriptInfo: Handle tsconfig.json text change (#25586)
* mapTextChangesToCodeEditsUsingScriptInfo: Handle tsconfig.json text change * Can't use `program.getSourceFile()` to determine file existence when multiple projects exist * Use direct union instead of discriminated union
1 parent f9764d1 commit af412e3

File tree

5 files changed

+73
-21
lines changed

5 files changed

+73
-21
lines changed

src/server/editorServices.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,15 @@ namespace ts.server {
17741774
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
17751775
}
17761776

1777+
/* @internal */
1778+
getScriptInfoOrConfig(uncheckedFileName: string): ScriptInfoOrConfig | undefined {
1779+
const path = toNormalizedPath(uncheckedFileName);
1780+
const info = this.getScriptInfoForNormalizedPath(path);
1781+
if (info) return info;
1782+
const configProject = this.configuredProjects.get(uncheckedFileName);
1783+
return configProject && configProject.getCompilerOptions().configFile;
1784+
}
1785+
17771786
/**
17781787
* Returns the projects that contain script info through SymLink
17791788
* Note that this does not return projects in info.containingProjects
@@ -2542,4 +2551,11 @@ namespace ts.server {
25422551
return false;
25432552
}
25442553
}
2554+
2555+
/* @internal */
2556+
export type ScriptInfoOrConfig = ScriptInfo | TsConfigSourceFile;
2557+
/* @internal */
2558+
export function isConfigFile(config: ScriptInfoOrConfig): config is TsConfigSourceFile {
2559+
return (config as TsConfigSourceFile).kind !== undefined;
2560+
}
25452561
}

src/server/session.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,7 +1763,7 @@ namespace ts.server {
17631763
this.projectService,
17641764
project => project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences),
17651765
(a, b) => a.fileName === b.fileName);
1766-
return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEditUsingScriptInfo(c)) : changes;
1766+
return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEditUsingScriptInfoOrConfigFile(c)) : changes;
17671767
}
17681768

17691769
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CodeFixAction> | ReadonlyArray<CodeFixAction> | undefined {
@@ -1840,8 +1840,8 @@ namespace ts.server {
18401840
return mapTextChangesToCodeEditsForFile(change, project.getSourceFileOrConfigFile(this.normalizePath(change.fileName)));
18411841
}
18421842

1843-
private mapTextChangeToCodeEditUsingScriptInfo(change: FileTextChanges): protocol.FileCodeEdits {
1844-
return mapTextChangesToCodeEditsUsingScriptInfo(change, this.projectService.getScriptInfo(this.normalizePath(change.fileName)));
1843+
private mapTextChangeToCodeEditUsingScriptInfoOrConfigFile(change: FileTextChanges): protocol.FileCodeEdits {
1844+
return mapTextChangesToCodeEditsUsingScriptInfoOrConfig(change, this.projectService.getScriptInfoOrConfig(this.normalizePath(change.fileName)));
18451845
}
18461846

18471847
private normalizePath(fileName: string) {
@@ -2358,7 +2358,7 @@ namespace ts.server {
23582358
}
23592359

23602360
function mapTextChangesToCodeEditsForFile(textChanges: FileTextChanges, sourceFile: SourceFile | undefined): protocol.FileCodeEdits {
2361-
Debug.assert(!!textChanges.isNewFile === !sourceFile, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: textChanges.isNewFile, hasSourceFile: !!sourceFile }));
2361+
Debug.assert(!!textChanges.isNewFile === !sourceFile, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasSourceFile: !!sourceFile }));
23622362
if (sourceFile) {
23632363
return {
23642364
fileName: textChanges.fileName,
@@ -2370,10 +2370,10 @@ namespace ts.server {
23702370
}
23712371
}
23722372

2373-
function mapTextChangesToCodeEditsUsingScriptInfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo | undefined): protocol.FileCodeEdits {
2374-
Debug.assert(!!textChanges.isNewFile === !scriptInfo);
2373+
function mapTextChangesToCodeEditsUsingScriptInfoOrConfig(textChanges: FileTextChanges, scriptInfo: ScriptInfoOrConfig | undefined): protocol.FileCodeEdits {
2374+
Debug.assert(!!textChanges.isNewFile === !scriptInfo, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasScriptInfo: !!scriptInfo }));
23752375
return scriptInfo
2376-
? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEditUsingScriptInfo(textChange, scriptInfo)) }
2376+
? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEditUsingScriptInfoOrConfig(textChange, scriptInfo)) }
23772377
: convertNewFileTextChangeToCodeEdit(textChanges);
23782378
}
23792379

@@ -2385,8 +2385,16 @@ namespace ts.server {
23852385
};
23862386
}
23872387

2388-
function convertTextChangeToCodeEditUsingScriptInfo(change: TextChange, scriptInfo: ScriptInfo) {
2389-
return { start: scriptInfo.positionToLineOffset(change.span.start), end: scriptInfo.positionToLineOffset(textSpanEnd(change.span)), newText: change.newText };
2388+
function convertTextChangeToCodeEditUsingScriptInfoOrConfig(change: TextChange, scriptInfo: ScriptInfoOrConfig): protocol.CodeEdit {
2389+
return { start: positionToLineOffset(scriptInfo, change.span.start), end: positionToLineOffset(scriptInfo, textSpanEnd(change.span)), newText: change.newText };
2390+
}
2391+
2392+
function positionToLineOffset(info: ScriptInfoOrConfig, position: number): protocol.Location {
2393+
return isConfigFile(info) ? locationFromLineAndCharacter(info.getLineAndCharacterOfPosition(position)) : info.positionToLineOffset(position);
2394+
}
2395+
2396+
function locationFromLineAndCharacter(lc: LineAndCharacter): protocol.Location {
2397+
return { line: lc.line + 1, offset: lc.character + 1 };
23902398
}
23912399

23922400
function convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits {

src/services/getEditsForFileRename.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ namespace ts {
151151
const toImport = oldFromNew !== undefined
152152
// If we're at the new location (file was already renamed), need to redo module resolution starting from the old location.
153153
// TODO:GH#18217
154-
? getSourceFileToImportFromResolved(resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), oldToNew, program)
154+
? getSourceFileToImportFromResolved(resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), oldToNew, host)
155155
: getSourceFileToImport(importedModuleSymbol, importLiteral, sourceFile, program, host, oldToNew);
156156

157157
// Need an update if the imported file moved, or the importing file moved and was using a relative path.
@@ -192,18 +192,18 @@ namespace ts {
192192
const resolved = host.resolveModuleNames
193193
? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName)
194194
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName);
195-
return getSourceFileToImportFromResolved(resolved, oldToNew, program);
195+
return getSourceFileToImportFromResolved(resolved, oldToNew, host);
196196
}
197197
}
198198

199-
function getSourceFileToImportFromResolved(resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, program: Program): ToImport | undefined {
199+
function getSourceFileToImportFromResolved(resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, host: LanguageServiceHost): ToImport | undefined {
200200
return resolved && (
201-
(resolved.resolvedModule && getIfInProgram(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, getIfInProgram));
201+
(resolved.resolvedModule && getIfExists(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, getIfExists));
202202

203-
function getIfInProgram(oldLocation: string): ToImport | undefined {
203+
function getIfExists(oldLocation: string): ToImport | undefined {
204204
const newLocation = oldToNew(oldLocation);
205205

206-
return program.getSourceFile(oldLocation) || newLocation !== undefined && program.getSourceFile(newLocation)
206+
return host.fileExists!(oldLocation) || newLocation !== undefined && host.fileExists!(newLocation) // TODO: GH#18217
207207
? newLocation !== undefined ? { newFileName: newLocation, updated: true } : { newFileName: oldLocation, updated: false }
208208
: undefined;
209209
}

src/testRunner/unittests/tsserverProjectSystem.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8688,7 +8688,7 @@ export const x = 10;`
86888688
};
86898689
const aTsconfig: File = {
86908690
path: "/a/tsconfig.json",
8691-
content: "{}",
8691+
content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }),
86928692
};
86938693
const bUserTs: File = {
86948694
path: "/b/user.ts",
@@ -8703,12 +8703,15 @@ export const x = 10;`
87038703
const session = createSession(host);
87048704
openFilesForSession([aUserTs, bUserTs], session);
87058705

8706-
const renameRequest = makeSessionRequest<protocol.GetEditsForFileRenameRequestArgs>(CommandNames.GetEditsForFileRename, {
8707-
oldFilePath: "/a/old.ts",
8706+
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
8707+
oldFilePath: aOldTs.path,
87088708
newFilePath: "/a/new.ts",
87098709
});
8710-
const response = session.executeCommand(renameRequest).response as protocol.GetEditsForFileRenameResponse["body"];
8711-
assert.deepEqual(response, [
8710+
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
8711+
{
8712+
fileName: aTsconfig.path,
8713+
textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }],
8714+
},
87128715
{
87138716
fileName: aUserTs.path,
87148717
textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }],
@@ -8719,6 +8722,31 @@ export const x = 10;`
87198722
},
87208723
]);
87218724
});
8725+
8726+
it("works with file moved to inferred project", () => {
8727+
const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' };
8728+
const cTs: File = { path: "/c.ts", content: "export {};" };
8729+
const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) };
8730+
8731+
const host = createServerHost([aTs, cTs, tsconfig]);
8732+
const session = createSession(host);
8733+
openFilesForSession([aTs, cTs], session);
8734+
8735+
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
8736+
oldFilePath: "/b.ts",
8737+
newFilePath: cTs.path,
8738+
});
8739+
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
8740+
{
8741+
fileName: "/tsconfig.json",
8742+
textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }],
8743+
},
8744+
{
8745+
fileName: "/a.ts",
8746+
textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }],
8747+
},
8748+
]);
8749+
});
87228750
});
87238751

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8893,7 +8893,7 @@ declare namespace ts.server {
88938893
private mapCodeFixAction;
88948894
private mapTextChangesToCodeEdits;
88958895
private mapTextChangeToCodeEdit;
8896-
private mapTextChangeToCodeEditUsingScriptInfo;
8896+
private mapTextChangeToCodeEditUsingScriptInfoOrConfigFile;
88978897
private normalizePath;
88988898
private convertTextChangeToCodeEdit;
88998899
private getBraceMatching;

0 commit comments

Comments
 (0)