Add 'renameFile' command to services (#23573)

* Add 'renameFile' command to services

* renameFile -> getEditsForFileRename

* Support `<reference path>` directives
This commit is contained in:
Andy
2018-04-20 13:43:09 -07:00
committed by GitHub
parent e65681a2b7
commit 5c94bef0e1
24 changed files with 211 additions and 17 deletions
+9
View File
@@ -2212,6 +2212,15 @@ namespace ts {
return absolutePath;
}
export function getRelativePath(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName) {
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return ensurePathIsRelative(relativePath);
}
export function ensurePathIsRelative(path: string): string {
return !pathIsRelative(path) ? "./" + path : path;
}
export function getBaseFileName(path: string) {
if (path === undefined) {
return undefined;
+6
View File
@@ -444,6 +444,12 @@ namespace ts {
}
}
export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined {
const containingDirectory = getDirectoryPath(containingFile);
const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory);
return perFolderCache && perFolderCache.get(moduleName);
}
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
if (traceEnabled) {
+6 -4
View File
@@ -622,9 +622,6 @@ namespace ts {
Debug.assert(!!missingFilePaths);
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
moduleResolutionCache = undefined;
// Release any files we have acquired in the old program but are
// not part of the new program.
if (oldProgram && host.onReleaseOldSourceFile) {
@@ -670,7 +667,8 @@ namespace ts {
sourceFileToPackageName,
redirectTargetsSet,
isEmittedFile,
getConfigFileParsingDiagnostics
getConfigFileParsingDiagnostics,
getResolvedModuleWithFailedLookupLocationsFromCache,
};
verifyCompilerOptions();
@@ -679,6 +677,10 @@ namespace ts {
return program;
function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations {
return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache);
}
function toPath(fileName: string): Path {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
+2
View File
@@ -2725,6 +2725,8 @@ namespace ts {
/* @internal */ redirectTargetsSet: Map<true>;
/** Is the file emitted file */
/* @internal */ isEmittedFile(file: string): boolean;
/* @internal */ getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined;
}
/* @internal */
+19
View File
@@ -3280,6 +3280,15 @@ Actual: ${stringify(fullActual)}`);
private static textSpansEqual(a: ts.TextSpan, b: ts.TextSpan) {
return a && b && a.start === b.start && a.length === b.length;
}
public getEditsForFileRename(options: FourSlashInterface.GetEditsForFileRenameOptions): void {
const changes = this.languageService.getEditsForFileRename(options.oldPath, options.newPath, this.formatCodeSettings);
this.applyChanges(changes);
for (const fileName in options.newFileContents) {
this.openFile(fileName);
this.verifyCurrentFileContent(options.newFileContents[fileName]);
}
}
}
export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) {
@@ -4371,6 +4380,10 @@ namespace FourSlashInterface {
public allRangesAppearInImplementationList(markerName: string) {
this.state.verifyRangesInImplementationList(markerName);
}
public getEditsForFileRename(options: GetEditsForFileRenameOptions) {
this.state.getEditsForFileRename(options);
}
}
export class Edit {
@@ -4713,4 +4726,10 @@ namespace FourSlashInterface {
range?: FourSlash.Range;
code: number;
}
export interface GetEditsForFileRenameOptions {
readonly oldPath: string;
readonly newPath: string;
readonly newFileContents: { readonly [fileName: string]: string };
}
}
+3
View File
@@ -528,6 +528,9 @@ namespace Harness.LanguageService {
organizeImports(_scope: ts.OrganizeImportsScope, _formatOptions: ts.FormatCodeSettings): ReadonlyArray<ts.FileTextChanges> {
throw new Error("Not supported on the shim.");
}
getEditsForFileRename(): ReadonlyArray<ts.FileTextChanges> {
throw new Error("Not supported on the shim.");
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
+1
View File
@@ -70,6 +70,7 @@
"../services/navigateTo.ts",
"../services/navigationBar.ts",
"../services/organizeImports.ts",
"../services/getEditsForFileRename.ts",
"../services/outliningElementsCollector.ts",
"../services/patternMatcher.ts",
"../services/preProcess.ts",
+2
View File
@@ -263,6 +263,8 @@ namespace ts.server {
CommandNames.GetEditsForRefactorFull,
CommandNames.OrganizeImports,
CommandNames.OrganizeImportsFull,
CommandNames.GetEditsForFileRename,
CommandNames.GetEditsForFileRenameFull,
];
it("should not throw when commands are executed with invalid arguments", () => {
+4
View File
@@ -632,6 +632,10 @@ namespace ts.server {
return notImplemented();
}
getEditsForFileRename() {
return notImplemented();
}
private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] {
return edits.map(edit => {
const fileName = edit.fileName;
+19
View File
@@ -121,6 +121,9 @@ namespace ts.server.protocol {
OrganizeImports = "organizeImports",
/* @internal */
OrganizeImportsFull = "organizeImports-full",
GetEditsForFileRename = "getEditsForFileRename",
/* @internal */
GetEditsForFileRenameFull = "getEditsForFileRename-full",
// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
}
@@ -610,6 +613,22 @@ namespace ts.server.protocol {
edits: ReadonlyArray<FileCodeEdits>;
}
export interface GetEditsForFileRenameRequest extends Request {
command: CommandTypes.GetEditsForFileRename;
arguments: GetEditsForFileRenameRequestArgs;
}
// Note: The file from FileRequestArgs is just any file in the project.
// We will generate code changes for every file in that project, so the choice is arbitrary.
export interface GetEditsForFileRenameRequestArgs extends FileRequestArgs {
readonly oldFilePath: string;
readonly newFilePath: string;
}
export interface GetEditsForFileRenameResponse extends Response {
edits: ReadonlyArray<FileCodeEdits>;
}
/**
* Request for the available codefixes at a specific position.
*/
+13 -1
View File
@@ -1665,6 +1665,12 @@ namespace ts.server {
}
}
private getEditsForFileRename(args: protocol.GetEditsForFileRenameRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.FileCodeEdits> | ReadonlyArray<FileTextChanges> {
const { file, project } = this.getFileAndProject(args);
const changes = project.getLanguageService().getEditsForFileRename(args.oldFilePath, args.newFilePath, this.getFormatOptions(file));
return simplifiedResult ? this.mapTextChangesToCodeEdits(project, changes) : changes;
}
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CodeFixAction> | ReadonlyArray<CodeFixAction> {
if (args.errorCodes.length === 0) {
return undefined;
@@ -2118,7 +2124,13 @@ namespace ts.server {
},
[CommandNames.OrganizeImportsFull]: (request: protocol.OrganizeImportsRequest) => {
return this.requiredResponse(this.organizeImports(request.arguments, /*simplifiedResult*/ false));
}
},
[CommandNames.GetEditsForFileRename]: (request: protocol.GetEditsForFileRenameRequest) => {
return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.GetEditsForFileRenameFull]: (request: protocol.GetEditsForFileRenameRequest) => {
return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ false));
},
});
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
+1
View File
@@ -66,6 +66,7 @@
"../services/navigateTo.ts",
"../services/navigationBar.ts",
"../services/organizeImports.ts",
"../services/getEditsForFileRename.ts",
"../services/outliningElementsCollector.ts",
"../services/patternMatcher.ts",
"../services/preProcess.ts",
+1
View File
@@ -72,6 +72,7 @@
"../services/navigateTo.ts",
"../services/navigationBar.ts",
"../services/organizeImports.ts",
"../services/getEditsForFileRename.ts",
"../services/outliningElementsCollector.ts",
"../services/patternMatcher.ts",
"../services/preProcess.ts",
+1 -7
View File
@@ -39,7 +39,6 @@ namespace ts.codefix {
}
function convertToImportCodeFixContext(context: CodeFixContext, symbolToken: Node, symbolName: string): ImportCodeFixContext {
const useCaseSensitiveFileNames = context.host.useCaseSensitiveFileNames ? context.host.useCaseSensitiveFileNames() : false;
const { program } = context;
const checker = program.getTypeChecker();
@@ -51,7 +50,7 @@ namespace ts.codefix {
checker,
compilerOptions: program.getCompilerOptions(),
cachedImportDeclarations: [],
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
getCanonicalFileName: createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(context.host)),
symbolName,
symbolToken,
preferences: context.preferences,
@@ -547,11 +546,6 @@ namespace ts.codefix {
return startsWith(path, "..");
}
function getRelativePath(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName) {
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return !pathIsRelative(relativePath) ? "./" + relativePath : relativePath;
}
function getCodeActionsForAddImport(
exportInfos: ReadonlyArray<SymbolExportInfo>,
ctx: ImportCodeFixContext,
+55
View File
@@ -0,0 +1,55 @@
/* @internal */
namespace ts {
export function getEditsForFileRename(program: Program, oldFilePath: string, newFilePath: string, host: LanguageServiceHost, formatContext: formatting.FormatContext): ReadonlyArray<FileTextChanges> {
const pathUpdater = getPathUpdater(oldFilePath, newFilePath, host);
return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => {
for (const { sourceFile, toUpdate } of getImportsToUpdate(program, oldFilePath)) {
const newPath = pathUpdater(isRef(toUpdate) ? toUpdate.fileName : toUpdate.text);
if (newPath !== undefined) {
const range = isRef(toUpdate) ? toUpdate : createTextRange(toUpdate.getStart(sourceFile) + 1, toUpdate.end - 1);
changeTracker.replaceRangeWithText(sourceFile, range, isRef(toUpdate) ? newPath : removeFileExtension(newPath));
}
}
});
}
interface ToUpdate {
readonly sourceFile: SourceFile;
readonly toUpdate: StringLiteralLike | FileReference;
}
function isRef(toUpdate: StringLiteralLike | FileReference): toUpdate is FileReference {
return "fileName" in toUpdate;
}
function getImportsToUpdate(program: Program, oldFilePath: string): ReadonlyArray<ToUpdate> {
const checker = program.getTypeChecker();
const result: ToUpdate[] = [];
for (const sourceFile of program.getSourceFiles()) {
for (const ref of sourceFile.referencedFiles) {
if (!program.getSourceFileFromReference(sourceFile, ref) && resolveTripleslashReference(ref.fileName, sourceFile.fileName) === oldFilePath) {
result.push({ sourceFile, toUpdate: ref });
}
}
for (const importStringLiteral of sourceFile.imports) {
// If it resolved to something already, ignore.
if (checker.getSymbolAtLocation(importStringLiteral)) continue;
const resolved = program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName);
if (contains(resolved.failedLookupLocations, oldFilePath)) {
result.push({ sourceFile, toUpdate: importStringLiteral });
}
}
}
return result;
}
function getPathUpdater(oldFilePath: string, newFilePath: string, host: LanguageServiceHost): (oldPath: string) => string | undefined {
// Get the relative path from old to new location, and append it on to the end of imports and normalize.
const rel = getRelativePath(newFilePath, getDirectoryPath(oldFilePath), createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host)));
return oldPath => {
if (!pathIsRelative(oldPath)) return;
return ensurePathIsRelative(normalizePath(combinePaths(getDirectoryPath(oldPath), rel)));
};
}
}
+8 -3
View File
@@ -1128,7 +1128,6 @@ namespace ts {
let lastProjectVersion: string;
let lastTypesRootVersion = 0;
const useCaseSensitivefileNames = host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames();
const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken());
const currentDirectory = host.getCurrentDirectory();
@@ -1145,7 +1144,8 @@ namespace ts {
}
}
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitivefileNames);
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
function getValidSourceFile(fileName: string): SourceFile {
const sourceFile = program.getSourceFile(fileName);
@@ -1202,7 +1202,7 @@ namespace ts {
getSourceFileByPath: getOrCreateSourceFileByPath,
getCancellationToken: () => cancellationToken,
getCanonicalFileName,
useCaseSensitiveFileNames: () => useCaseSensitivefileNames,
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)),
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
writeFile: noop,
@@ -1951,6 +1951,10 @@ namespace ts {
return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences);
}
function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges> {
return ts.getEditsForFileRename(getProgram(), oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions));
}
function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
function applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
@@ -2251,6 +2255,7 @@ namespace ts {
getCombinedCodeFix,
applyCodeActionCommand,
organizeImports,
getEditsForFileRename,
getEmitOutput,
getNonBoundSourceFile,
getSourceFile,
+5 -1
View File
@@ -365,8 +365,12 @@ namespace ts.textChanges {
this.insertText(sourceFile, token.getStart(sourceFile), text);
}
public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string) {
this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text });
}
private insertText(sourceFile: SourceFile, pos: number, text: string): void {
this.changes.push({ kind: ChangeKind.Text, sourceFile, range: { pos, end: pos }, text });
this.replaceRangeWithText(sourceFile, createTextRange(pos), text);
}
/** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */
+1
View File
@@ -63,6 +63,7 @@
"navigateTo.ts",
"navigationBar.ts",
"organizeImports.ts",
"getEditsForFileRename.ts",
"outliningElementsCollector.ts",
"patternMatcher.ts",
"preProcess.ts",
+1
View File
@@ -334,6 +334,7 @@ namespace ts {
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange, preferences: UserPreferences | undefined): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined;
organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges>;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
+8
View File
@@ -1213,6 +1213,14 @@ namespace ts {
? isStringOrNumericLiteral(name.expression) ? name.expression.text : undefined
: getTextOfIdentifierOrLiteral(name);
}
export function hostUsesCaseSensitiveFileNames(host: LanguageServiceHost): boolean {
return host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false;
}
export function hostGetCanonicalFileName(host: LanguageServiceHost): GetCanonicalFileName {
return createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host));
}
}
// Display-part writer helpers
+16 -1
View File
@@ -3402,6 +3402,7 @@ declare namespace ts {
set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void;
}
function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache;
function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined;
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations;
function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations;
function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations;
@@ -4446,6 +4447,7 @@ declare namespace ts {
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange, preferences: UserPreferences | undefined): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined;
organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges>;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getProgram(): Program;
dispose(): void;
@@ -5410,7 +5412,8 @@ declare namespace ts.server.protocol {
GetSupportedCodeFixes = "getSupportedCodeFixes",
GetApplicableRefactors = "getApplicableRefactors",
GetEditsForRefactor = "getEditsForRefactor",
OrganizeImports = "organizeImports"
OrganizeImports = "organizeImports",
GetEditsForFileRename = "getEditsForFileRename"
}
/**
* A TypeScript Server message
@@ -5806,6 +5809,17 @@ declare namespace ts.server.protocol {
interface OrganizeImportsResponse extends Response {
edits: ReadonlyArray<FileCodeEdits>;
}
interface GetEditsForFileRenameRequest extends Request {
command: CommandTypes.GetEditsForFileRename;
arguments: GetEditsForFileRenameRequestArgs;
}
interface GetEditsForFileRenameRequestArgs extends FileRequestArgs {
readonly oldFilePath: string;
readonly newFilePath: string;
}
interface GetEditsForFileRenameResponse extends Response {
edits: ReadonlyArray<FileCodeEdits>;
}
/**
* Request for the available codefixes at a specific position.
*/
@@ -8322,6 +8336,7 @@ declare namespace ts.server {
private getApplicableRefactors;
private getEditsForRefactor;
private organizeImports;
private getEditsForFileRename;
private getCodeFixes;
private getCombinedCodeFix;
private applyCodeActionCommand;
+2
View File
@@ -3402,6 +3402,7 @@ declare namespace ts {
set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void;
}
function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache;
function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined;
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations;
function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations;
function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations;
@@ -4446,6 +4447,7 @@ declare namespace ts {
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange, preferences: UserPreferences | undefined): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined;
organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges>;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getProgram(): Program;
dispose(): void;
+5
View File
@@ -345,6 +345,11 @@ declare namespace FourSlashInterface {
getSuggestionDiagnostics(expected: ReadonlyArray<Diagnostic>): void;
ProjectInfo(expected: string[]): void;
allRangesAppearInImplementationList(markerName: string): void;
getEditsForFileRename(options: {
oldPath: string;
newPath: string;
newFileContents: { [fileName: string]: string };
});
}
class edit {
backspace(count?: number): void;
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////// <reference path="./src/old.ts" />
////import old from "./src/old";
// @Filename: /src/a.ts
/////// <reference path="./old.ts" />
////import old from "./old";
// @Filename: /src/foo/a.ts
/////// <reference path="../old.ts" />
////import old from "../old";
verify.getEditsForFileRename({
oldPath: "/src/old.ts",
newPath: "/src/new.ts",
newFileContents: {
"/a.ts": '/// <reference path="./src/new.ts" />\nimport old from "./src/new";',
"/src/a.ts": '/// <reference path="./new.ts" />\nimport old from "./new";',
"/src/foo/a.ts": '/// <reference path="../new.ts" />\nimport old from "../new";',
},
});