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
This commit is contained in:
Andy 2018-07-12 12:09:04 -07:00 committed by GitHub
parent f9764d17f0
commit af412e39cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 21 deletions

View File

@ -1774,6 +1774,15 @@ namespace ts.server {
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
}
/* @internal */
getScriptInfoOrConfig(uncheckedFileName: string): ScriptInfoOrConfig | undefined {
const path = toNormalizedPath(uncheckedFileName);
const info = this.getScriptInfoForNormalizedPath(path);
if (info) return info;
const configProject = this.configuredProjects.get(uncheckedFileName);
return configProject && configProject.getCompilerOptions().configFile;
}
/**
* Returns the projects that contain script info through SymLink
* Note that this does not return projects in info.containingProjects
@ -2542,4 +2551,11 @@ namespace ts.server {
return false;
}
}
/* @internal */
export type ScriptInfoOrConfig = ScriptInfo | TsConfigSourceFile;
/* @internal */
export function isConfigFile(config: ScriptInfoOrConfig): config is TsConfigSourceFile {
return (config as TsConfigSourceFile).kind !== undefined;
}
}

View File

@ -1763,7 +1763,7 @@ namespace ts.server {
this.projectService,
project => project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences),
(a, b) => a.fileName === b.fileName);
return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEditUsingScriptInfo(c)) : changes;
return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEditUsingScriptInfoOrConfigFile(c)) : changes;
}
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CodeFixAction> | ReadonlyArray<CodeFixAction> | undefined {
@ -1840,8 +1840,8 @@ namespace ts.server {
return mapTextChangesToCodeEditsForFile(change, project.getSourceFileOrConfigFile(this.normalizePath(change.fileName)));
}
private mapTextChangeToCodeEditUsingScriptInfo(change: FileTextChanges): protocol.FileCodeEdits {
return mapTextChangesToCodeEditsUsingScriptInfo(change, this.projectService.getScriptInfo(this.normalizePath(change.fileName)));
private mapTextChangeToCodeEditUsingScriptInfoOrConfigFile(change: FileTextChanges): protocol.FileCodeEdits {
return mapTextChangesToCodeEditsUsingScriptInfoOrConfig(change, this.projectService.getScriptInfoOrConfig(this.normalizePath(change.fileName)));
}
private normalizePath(fileName: string) {
@ -2358,7 +2358,7 @@ namespace ts.server {
}
function mapTextChangesToCodeEditsForFile(textChanges: FileTextChanges, sourceFile: SourceFile | undefined): protocol.FileCodeEdits {
Debug.assert(!!textChanges.isNewFile === !sourceFile, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: textChanges.isNewFile, hasSourceFile: !!sourceFile }));
Debug.assert(!!textChanges.isNewFile === !sourceFile, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasSourceFile: !!sourceFile }));
if (sourceFile) {
return {
fileName: textChanges.fileName,
@ -2370,10 +2370,10 @@ namespace ts.server {
}
}
function mapTextChangesToCodeEditsUsingScriptInfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo | undefined): protocol.FileCodeEdits {
Debug.assert(!!textChanges.isNewFile === !scriptInfo);
function mapTextChangesToCodeEditsUsingScriptInfoOrConfig(textChanges: FileTextChanges, scriptInfo: ScriptInfoOrConfig | undefined): protocol.FileCodeEdits {
Debug.assert(!!textChanges.isNewFile === !scriptInfo, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasScriptInfo: !!scriptInfo }));
return scriptInfo
? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEditUsingScriptInfo(textChange, scriptInfo)) }
? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEditUsingScriptInfoOrConfig(textChange, scriptInfo)) }
: convertNewFileTextChangeToCodeEdit(textChanges);
}
@ -2385,8 +2385,16 @@ namespace ts.server {
};
}
function convertTextChangeToCodeEditUsingScriptInfo(change: TextChange, scriptInfo: ScriptInfo) {
return { start: scriptInfo.positionToLineOffset(change.span.start), end: scriptInfo.positionToLineOffset(textSpanEnd(change.span)), newText: change.newText };
function convertTextChangeToCodeEditUsingScriptInfoOrConfig(change: TextChange, scriptInfo: ScriptInfoOrConfig): protocol.CodeEdit {
return { start: positionToLineOffset(scriptInfo, change.span.start), end: positionToLineOffset(scriptInfo, textSpanEnd(change.span)), newText: change.newText };
}
function positionToLineOffset(info: ScriptInfoOrConfig, position: number): protocol.Location {
return isConfigFile(info) ? locationFromLineAndCharacter(info.getLineAndCharacterOfPosition(position)) : info.positionToLineOffset(position);
}
function locationFromLineAndCharacter(lc: LineAndCharacter): protocol.Location {
return { line: lc.line + 1, offset: lc.character + 1 };
}
function convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits {

View File

@ -151,7 +151,7 @@ namespace ts {
const toImport = oldFromNew !== undefined
// If we're at the new location (file was already renamed), need to redo module resolution starting from the old location.
// TODO:GH#18217
? getSourceFileToImportFromResolved(resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), oldToNew, program)
? getSourceFileToImportFromResolved(resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), oldToNew, host)
: getSourceFileToImport(importedModuleSymbol, importLiteral, sourceFile, program, host, oldToNew);
// 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 {
const resolved = host.resolveModuleNames
? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName)
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName);
return getSourceFileToImportFromResolved(resolved, oldToNew, program);
return getSourceFileToImportFromResolved(resolved, oldToNew, host);
}
}
function getSourceFileToImportFromResolved(resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, program: Program): ToImport | undefined {
function getSourceFileToImportFromResolved(resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, host: LanguageServiceHost): ToImport | undefined {
return resolved && (
(resolved.resolvedModule && getIfInProgram(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, getIfInProgram));
(resolved.resolvedModule && getIfExists(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, getIfExists));
function getIfInProgram(oldLocation: string): ToImport | undefined {
function getIfExists(oldLocation: string): ToImport | undefined {
const newLocation = oldToNew(oldLocation);
return program.getSourceFile(oldLocation) || newLocation !== undefined && program.getSourceFile(newLocation)
return host.fileExists!(oldLocation) || newLocation !== undefined && host.fileExists!(newLocation) // TODO: GH#18217
? newLocation !== undefined ? { newFileName: newLocation, updated: true } : { newFileName: oldLocation, updated: false }
: undefined;
}

View File

@ -8688,7 +8688,7 @@ export const x = 10;`
};
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: "{}",
content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }),
};
const bUserTs: File = {
path: "/b/user.ts",
@ -8703,12 +8703,15 @@ export const x = 10;`
const session = createSession(host);
openFilesForSession([aUserTs, bUserTs], session);
const renameRequest = makeSessionRequest<protocol.GetEditsForFileRenameRequestArgs>(CommandNames.GetEditsForFileRename, {
oldFilePath: "/a/old.ts",
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
oldFilePath: aOldTs.path,
newFilePath: "/a/new.ts",
});
const response = session.executeCommand(renameRequest).response as protocol.GetEditsForFileRenameResponse["body"];
assert.deepEqual(response, [
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
{
fileName: aTsconfig.path,
textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }],
},
{
fileName: aUserTs.path,
textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }],
@ -8719,6 +8722,31 @@ export const x = 10;`
},
]);
});
it("works with file moved to inferred project", () => {
const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' };
const cTs: File = { path: "/c.ts", content: "export {};" };
const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) };
const host = createServerHost([aTs, cTs, tsconfig]);
const session = createSession(host);
openFilesForSession([aTs, cTs], session);
const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, {
oldFilePath: "/b.ts",
newFilePath: cTs.path,
});
assert.deepEqual<ReadonlyArray<protocol.FileCodeEdits>>(response, [
{
fileName: "/tsconfig.json",
textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }],
},
{
fileName: "/a.ts",
textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }],
},
]);
});
});
describe("tsserverProjectSystem document registry in project service", () => {

View File

@ -8893,7 +8893,7 @@ declare namespace ts.server {
private mapCodeFixAction;
private mapTextChangesToCodeEdits;
private mapTextChangeToCodeEdit;
private mapTextChangeToCodeEditUsingScriptInfo;
private mapTextChangeToCodeEditUsingScriptInfoOrConfigFile;
private normalizePath;
private convertTextChangeToCodeEdit;
private getBraceMatching;