getEditsForFileRename: Support directory rename (#24305) (#24568)

* getEditsForFileRename: Support directory rename

* Code review

* Handle imports inside the new file/directory

* Document path updaters

* Shorten relative paths where possible

* Reduce duplicate code

* Rewrite, use moduleSpecifiers.ts to get module specifiers from scratch instead of updating relative paths

* Update additional tsconfig.json fields

* Add test with '.js' extension

* Handle case-insensitive paths

* Better tsconfig handling

* Handle properties inside compilerOptions

* Use getOptionFromName
This commit is contained in:
Andy
2018-06-01 09:48:48 -07:00
committed by GitHub
parent 19ef19489c
commit 8dc7d5aa88
29 changed files with 792 additions and 154 deletions
+2 -1
View File
@@ -929,7 +929,8 @@ namespace ts {
}
}
function getOptionFromName(optionName: string, allowShort = false): CommandLineOption | undefined {
/** @internal */
export function getOptionFromName(optionName: string, allowShort = false): CommandLineOption | undefined {
optionName = optionName.toLowerCase();
const { optionNameMap, shortOptionNames } = getOptionNameMap();
// Try to translate short option names to their full equivalents.
+31 -6
View File
@@ -2557,6 +2557,22 @@ namespace ts {
return startsWith(str, prefix) ? str.substr(prefix.length) : str;
}
export function tryRemovePrefix(str: string, prefix: string): string | undefined {
return startsWith(str, prefix) ? str.substring(prefix.length) : undefined;
}
export function tryRemoveDirectoryPrefix(path: string, dirPath: string): string | undefined {
const a = tryRemovePrefix(path, dirPath);
if (a === undefined) return undefined;
switch (a.charCodeAt(0)) {
case CharacterCodes.slash:
case CharacterCodes.backslash:
return a.slice(1);
default:
return undefined;
}
}
export function endsWith(str: string, suffix: string): boolean {
const expectedPos = str.length - suffix.length;
return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos;
@@ -2566,6 +2582,10 @@ namespace ts {
return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str;
}
export function tryRemoveSuffix(str: string, suffix: string): string | undefined {
return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined;
}
export function stringContains(str: string, substring: string): boolean {
return str.indexOf(substring) !== -1;
}
@@ -2766,7 +2786,8 @@ namespace ts {
basePaths: ReadonlyArray<string>;
}
export function getFileMatcherPatterns(path: string, excludes: ReadonlyArray<string>, includes: ReadonlyArray<string>, useCaseSensitiveFileNames: boolean, currentDirectory: string): FileMatcherPatterns {
/** @param path directory of the tsconfig.json */
export function getFileMatcherPatterns(path: string, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string> | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string): FileMatcherPatterns {
path = normalizePath(path);
currentDirectory = normalizePath(currentDirectory);
const absolutePath = combinePaths(currentDirectory, path);
@@ -2780,16 +2801,20 @@ namespace ts {
};
}
export function matchFiles(path: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string>, includes: ReadonlyArray<string>, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries): string[] {
export function getRegexFromPattern(pattern: string, useCaseSensitiveFileNames: boolean): RegExp {
return new RegExp(pattern, useCaseSensitiveFileNames ? "" : "i");
}
/** @param path directory of the tsconfig.json */
export function matchFiles(path: string, extensions: ReadonlyArray<string> | undefined, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string> | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries): string[] {
path = normalizePath(path);
currentDirectory = normalizePath(currentDirectory);
const patterns = getFileMatcherPatterns(path, excludes, includes, useCaseSensitiveFileNames, currentDirectory);
const regexFlag = useCaseSensitiveFileNames ? "" : "i";
const includeFileRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => new RegExp(pattern, regexFlag));
const includeDirectoryRegex = patterns.includeDirectoryPattern && new RegExp(patterns.includeDirectoryPattern, regexFlag);
const excludeRegex = patterns.excludePattern && new RegExp(patterns.excludePattern, regexFlag);
const includeFileRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => getRegexFromPattern(pattern, useCaseSensitiveFileNames));
const includeDirectoryRegex = patterns.includeDirectoryPattern && getRegexFromPattern(patterns.includeDirectoryPattern, useCaseSensitiveFileNames);
const excludeRegex = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, useCaseSensitiveFileNames);
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
// If there are no "includes", then just put everything in results[0].
+9 -6
View File
@@ -1082,7 +1082,7 @@ namespace ts {
}
export function getPropertyAssignment(objectLiteral: ObjectLiteralExpression, key: string, key2?: string): ReadonlyArray<PropertyAssignment> {
return filter(objectLiteral.properties, (property): property is PropertyAssignment => {
return objectLiteral.properties.filter((property): property is PropertyAssignment => {
if (property.kind === SyntaxKind.PropertyAssignment) {
const propName = getTextOfPropertyName(property.name);
return key === propName || (key2 && key2 === propName);
@@ -1098,12 +1098,15 @@ namespace ts {
}
export function getTsConfigPropArrayElementValue(tsConfigSourceFile: TsConfigSourceFile | undefined, propKey: string, elementValue: string): StringLiteral | undefined {
return firstDefined(getTsConfigPropArray(tsConfigSourceFile, propKey), property =>
isArrayLiteralExpression(property.initializer) ?
find(property.initializer.elements, (element): element is StringLiteral => isStringLiteral(element) && element.text === elementValue) :
undefined);
}
export function getTsConfigPropArray(tsConfigSourceFile: TsConfigSourceFile | undefined, propKey: string): ReadonlyArray<PropertyAssignment> {
const jsonObjectLiteral = getTsConfigObjectLiteralExpression(tsConfigSourceFile);
return jsonObjectLiteral &&
firstDefined(getPropertyAssignment(jsonObjectLiteral, propKey), property =>
isArrayLiteralExpression(property.initializer) ?
find(property.initializer.elements, (element): element is StringLiteral => isStringLiteral(element) && element.text === elementValue) :
undefined);
return jsonObjectLiteral ? getPropertyAssignment(jsonObjectLiteral, propKey) : emptyArray;
}
export function getContainingFunction(node: Node): SignatureDeclaration {
+10 -10
View File
@@ -3149,8 +3149,12 @@ Actual: ${stringify(fullActual)}`);
assert(action.name === "Move to a new file" && action.description === "Move to a new file");
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.defaultPreferences)!;
for (const edit of editInfo.edits) {
const newContent = options.newFileContents[edit.fileName];
this.testNewFileContents(editInfo.edits, options.newFileContents);
}
private testNewFileContents(edits: ReadonlyArray<ts.FileTextChanges>, newFileContents: { [fileName: string]: string }): void {
for (const edit of edits) {
const newContent = newFileContents[edit.fileName];
if (newContent === undefined) {
this.raiseError(`There was an edit in ${edit.fileName} but new content was not specified.`);
}
@@ -3167,8 +3171,8 @@ Actual: ${stringify(fullActual)}`);
}
}
for (const fileName in options.newFileContents) {
assert(editInfo.edits.some(e => e.fileName === fileName));
for (const fileName in newFileContents) {
assert(edits.some(e => e.fileName === fileName));
}
}
@@ -3378,12 +3382,8 @@ Actual: ${stringify(fullActual)}`);
}
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]);
}
const changes = this.languageService.getEditsForFileRename(options.oldPath, options.newPath, this.formatCodeSettings, ts.defaultPreferences);
this.testNewFileContents(changes, options.newFileContents);
}
private getApplicableRefactors(positionOrRange: number | ts.TextRange, preferences = ts.defaultPreferences): ReadonlyArray<ts.ApplicableRefactorInfo> {
+11 -3
View File
@@ -8407,15 +8407,23 @@ new C();`
path: "/user.ts",
content: 'import { x } from "./old";',
};
const newTs: File = {
path: "/new.ts",
content: "export const x = 0;",
};
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const host = createServerHost([userTs]);
const host = createServerHost([userTs, newTs, tsconfig]);
const projectService = createProjectService(host);
projectService.openClientFile(userTs.path);
const project = first(projectService.inferredProjects);
const project = projectService.configuredProjects.get(tsconfig.path)!;
Debug.assert(!!project.resolveModuleNames);
const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatOptions);
const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatOptions, defaultPreferences);
assert.deepEqual<ReadonlyArray<FileTextChanges>>(edits, [{
fileName: "/user.ts",
textChanges: [{
+1 -1
View File
@@ -1695,7 +1695,7 @@ 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));
const changes = project.getLanguageService().getEditsForFileRename(args.oldFilePath, args.newFilePath, this.getFormatOptions(file), this.getPreferences(file));
return simplifiedResult ? this.mapTextChangesToCodeEdits(project, changes) : changes;
}
+100 -63
View File
@@ -1,6 +1,14 @@
// Used by importFixes to synthesize import module specifiers.
/* @internal */
namespace ts.moduleSpecifiers {
// Note: fromSourceFile is just for usesJsExtensionOnImports
export function getModuleSpecifier(program: Program, fromSourceFile: SourceFile, fromSourceFileName: string, toFileName: string, host: LanguageServiceHost, preferences: UserPreferences) {
const info = getInfo(program.getCompilerOptions(), fromSourceFile, fromSourceFileName, host);
const compilerOptions = program.getCompilerOptions();
return getGlobalModuleSpecifier(toFileName, info, host, compilerOptions) ||
first(getLocalModuleSpecifiers(toFileName, info, compilerOptions, preferences));
}
// For each symlink/original for a module, returns a list of ways to import that file.
export function getModuleSpecifiers(
moduleSymbol: Symbol,
@@ -9,81 +17,110 @@ namespace ts.moduleSpecifiers {
host: LanguageServiceHost,
preferences: UserPreferences,
): ReadonlyArray<ReadonlyArray<string>> {
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
if (ambient) return [[ambient]];
const compilerOptions = program.getCompilerOptions();
const { baseUrl, paths, rootDirs } = compilerOptions;
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.fileName, host);
const modulePaths = getAllModulePaths(program, moduleSymbol.valueDeclaration.getSourceFile());
const global = mapDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions));
return global.length ? global.map(g => [g]) : modulePaths.map(moduleFileName =>
getLocalModuleSpecifiers(moduleFileName, info, compilerOptions, preferences));
}
interface Info {
readonly moduleResolutionKind: ModuleResolutionKind;
readonly addJsExtension: boolean;
readonly getCanonicalFileName: GetCanonicalFileName;
readonly sourceDirectory: string;
}
// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path
function getInfo(compilerOptions: CompilerOptions, importingSourceFile: SourceFile, importingSourceFileName: string, host: LanguageServiceHost): Info {
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const addJsExtension = usesJsExtensionOnImports(importingSourceFile);
const getCanonicalFileName = hostGetCanonicalFileName(host);
const sourceDirectory = getDirectoryPath(importingSourceFile.fileName);
const sourceDirectory = getDirectoryPath(importingSourceFileName);
return { moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory };
}
return getAllModulePaths(program, moduleSymbol.valueDeclaration.getSourceFile()).map(moduleFileName => {
const global = tryGetModuleNameFromAmbientModule(moduleSymbol)
|| tryGetModuleNameFromTypeRoots(compilerOptions, host, getCanonicalFileName, moduleFileName, addJsExtension)
|| tryGetModuleNameAsNodeModule(compilerOptions, moduleFileName, host, getCanonicalFileName, sourceDirectory)
|| rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName);
if (global) {
return [global];
function getGlobalModuleSpecifier(
moduleFileName: string,
{ addJsExtension, getCanonicalFileName, sourceDirectory }: Info,
host: LanguageServiceHost,
compilerOptions: CompilerOptions,
) {
return tryGetModuleNameFromTypeRoots(compilerOptions, host, getCanonicalFileName, moduleFileName, addJsExtension)
|| tryGetModuleNameAsNodeModule(compilerOptions, moduleFileName, host, getCanonicalFileName, sourceDirectory)
|| compilerOptions.rootDirs && tryGetModuleNameFromRootDirs(compilerOptions.rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName);
}
function getLocalModuleSpecifiers(
moduleFileName: string,
{ moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory }: Info,
compilerOptions: CompilerOptions,
preferences: UserPreferences,
) {
const { baseUrl, paths } = compilerOptions;
const relativePath = removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), moduleResolutionKind, addJsExtension);
if (!baseUrl || preferences.importModuleSpecifierPreference === "relative") {
return [relativePath];
}
const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName);
if (!relativeToBaseUrl) {
return [relativePath];
}
const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, moduleResolutionKind, addJsExtension);
if (paths) {
const fromPaths = tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths);
if (fromPaths) {
return [fromPaths];
}
}
const relativePath = removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), moduleResolutionKind, addJsExtension);
if (!baseUrl || preferences.importModuleSpecifierPreference === "relative") {
return [relativePath];
}
if (preferences.importModuleSpecifierPreference === "non-relative") {
return [importRelativeToBaseUrl];
}
const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName);
if (!relativeToBaseUrl) {
return [relativePath];
}
if (preferences.importModuleSpecifierPreference !== undefined) Debug.assertNever(preferences.importModuleSpecifierPreference);
const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, moduleResolutionKind, addJsExtension);
if (paths) {
const fromPaths = tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths);
if (fromPaths) {
return [fromPaths];
}
}
if (isPathRelativeToParent(relativeToBaseUrl)) {
return [relativePath];
}
if (preferences.importModuleSpecifierPreference === "non-relative") {
return [importRelativeToBaseUrl];
}
/*
Prefer a relative import over a baseUrl import if it doesn't traverse up to baseUrl.
if (preferences.importModuleSpecifierPreference !== undefined) Debug.assertNever(preferences.importModuleSpecifierPreference);
Suppose we have:
baseUrl = /base
sourceDirectory = /base/a/b
moduleFileName = /base/foo/bar
Then:
relativePath = ../../foo/bar
getRelativePathNParents(relativePath) = 2
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
2 < 2 = false
In this case we should prefer using the baseUrl path "/a/b" instead of the relative path "../../foo/bar".
if (isPathRelativeToParent(relativeToBaseUrl)) {
return [relativePath];
}
/*
Prefer a relative import over a baseUrl import if it doesn't traverse up to baseUrl.
Suppose we have:
baseUrl = /base
sourceDirectory = /base/a/b
moduleFileName = /base/foo/bar
Then:
relativePath = ../../foo/bar
getRelativePathNParents(relativePath) = 2
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
2 < 2 = false
In this case we should prefer using the baseUrl path "/a/b" instead of the relative path "../../foo/bar".
Suppose we have:
baseUrl = /base
sourceDirectory = /base/foo/a
moduleFileName = /base/foo/bar
Then:
relativePath = ../a
getRelativePathNParents(relativePath) = 1
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
1 < 2 = true
In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a".
*/
const pathFromSourceToBaseUrl = ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, baseUrl, getCanonicalFileName));
const relativeFirst = getRelativePathNParents(relativePath) < getRelativePathNParents(pathFromSourceToBaseUrl);
return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath];
});
Suppose we have:
baseUrl = /base
sourceDirectory = /base/foo/a
moduleFileName = /base/foo/bar
Then:
relativePath = ../a
getRelativePathNParents(relativePath) = 1
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
1 < 2 = true
In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a".
*/
const pathFromSourceToBaseUrl = ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, baseUrl, getCanonicalFileName));
const relativeFirst = getRelativePathNParents(relativePath) < getRelativePathNParents(pathFromSourceToBaseUrl);
return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath];
}
function usesJsExtensionOnImports({ imports }: SourceFile): boolean {
+176 -54
View File
@@ -1,67 +1,189 @@
/* @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);
export function getEditsForFileRename(program: Program, oldFileOrDirPath: string, newFileOrDirPath: string, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences): ReadonlyArray<FileTextChanges> {
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName);
const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName);
return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => {
updateTsconfigFiles(program, changeTracker, oldFilePath, newFilePath);
for (const { sourceFile, toUpdate } of getImportsToUpdate(program, oldFilePath, host)) {
const newPath = pathUpdater(isRef(toUpdate) ? toUpdate.fileName : toUpdate.text);
if (newPath !== undefined) {
const range = isRef(toUpdate) ? toUpdate : createStringRange(toUpdate, sourceFile);
changeTracker.replaceRangeWithText(sourceFile, range, isRef(toUpdate) ? newPath : removeFileExtension(newPath));
}
}
updateTsconfigFiles(program, changeTracker, oldToNew, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames);
updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName, preferences);
});
}
function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldFilePath: string, newFilePath: string): void {
const configFile = program.getCompilerOptions().configFile;
const oldFile = getTsConfigPropArrayElementValue(configFile, "files", oldFilePath);
if (oldFile) {
changeTracker.replaceRangeWithText(configFile, createStringRange(oldFile, configFile), newFilePath);
}
}
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, host: LanguageServiceHost): ReadonlyArray<ToUpdate> {
const result: ToUpdate[] = [];
for (const sourceFile of program.getSourceFiles()) {
for (const ref of sourceFile.referencedFiles) {
if (resolveTripleslashReference(ref.fileName, sourceFile.fileName) === oldFilePath) {
result.push({ sourceFile, toUpdate: ref });
}
}
for (const importStringLiteral of sourceFile.imports) {
const resolved = host.resolveModuleNames
? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName)
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName);
// We may or may not have picked up on the file being renamed, so maybe successfully resolved to oldFilePath, or maybe that's in failedLookupLocations
if (resolved && contains(resolved.resolvedModule ? [resolved.resolvedModule.resolvedFileName] : 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 = getRelativePathFromFile(oldFilePath, newFilePath, createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host)));
return oldPath => {
if (!pathIsRelative(oldPath)) return;
return ensurePathIsNonModuleName(normalizePath(combinePaths(getDirectoryPath(oldPath), rel)));
/** If 'path' refers to an old directory, returns path in the new directory. */
type PathUpdater = (path: string) => string | undefined;
function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName): PathUpdater {
const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath);
return path => {
const canonicalPath = getCanonicalFileName(path);
if (canonicalPath === canonicalOldPath) return newFileOrDirPath;
const suffix = tryRemoveDirectoryPrefix(canonicalPath, canonicalOldPath);
return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix;
};
}
function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void {
const { configFile } = program.getCompilerOptions();
if (!configFile) return;
const configDir = getDirectoryPath(configFile.fileName);
const jsonObjectLiteral = getTsConfigObjectLiteralExpression(configFile);
if (!jsonObjectLiteral) return;
forEachProperty(jsonObjectLiteral, (property, propertyName) => {
switch (propertyName) {
case "files":
case "include":
case "exclude": {
const foundExactMatch = updatePaths(property);
if (!foundExactMatch && propertyName === "include" && isArrayLiteralExpression(property.initializer)) {
const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined);
const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory);
// If there isn't some include for this, add a new one.
if (!getRegexFromPattern(Debug.assertDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) {
changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), createStringLiteral(relativePath(newFileOrDirPath)));
}
}
break;
}
case "compilerOptions":
forEachProperty(property.initializer, (property, propertyName) => {
const option = getOptionFromName(propertyName);
if (option && (option.isFilePath || option.type === "list" && (option as CommandLineOptionOfListType).element.isFilePath)) {
updatePaths(property);
}
else if (propertyName === "paths") {
forEachProperty(property.initializer, (pathsProperty) => {
if (!isArrayLiteralExpression(pathsProperty.initializer)) return;
for (const e of pathsProperty.initializer.elements) {
tryUpdateString(e);
}
});
}
});
break;
}
});
function updatePaths(property: PropertyAssignment): boolean {
// Type annotation needed due to #7294
const elements: ReadonlyArray<Expression> = isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer];
let foundExactMatch = false;
for (const element of elements) {
foundExactMatch = tryUpdateString(element) || foundExactMatch;
}
return foundExactMatch;
}
function tryUpdateString(element: Expression): boolean {
if (!isStringLiteral(element)) return false;
const elementFileName = combinePathsSafe(configDir, element.text);
const updated = oldToNew(elementFileName);
if (updated !== undefined) {
changeTracker.replaceRangeWithText(configFile!, createStringRange(element, configFile!), relativePath(updated));
return true;
}
return false;
}
function relativePath(path: string): string {
return getRelativePathFromDirectory(configDir, path, /*ignoreCase*/ !useCaseSensitiveFileNames);
}
}
function updateImports(
program: Program,
changeTracker: textChanges.ChangeTracker,
oldToNew: PathUpdater,
newToOld: PathUpdater,
host: LanguageServiceHost,
getCanonicalFileName: GetCanonicalFileName,
preferences: UserPreferences,
): void {
for (const sourceFile of program.getSourceFiles()) {
const newImportFromPath = oldToNew(sourceFile.fileName) || sourceFile.fileName;
const newImportFromDirectory = getDirectoryPath(newImportFromPath);
const oldFromNew: string | undefined = newToOld(sourceFile.fileName);
const oldImportFromPath: string = oldFromNew || sourceFile.fileName;
const oldImportFromDirectory = getDirectoryPath(oldImportFromPath);
updateImportsWorker(sourceFile, changeTracker,
referenceText => {
if (!pathIsRelative(referenceText)) return undefined;
const oldAbsolute = combinePathsSafe(oldImportFromDirectory, referenceText);
const newAbsolute = oldToNew(oldAbsolute);
return newAbsolute === undefined ? undefined : ensurePathIsNonModuleName(getRelativePathFromDirectory(newImportFromDirectory, newAbsolute, getCanonicalFileName));
},
importLiteral => {
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)
: getSourceFileToImport(importLiteral, sourceFile, program, host, oldToNew);
return toImport === undefined ? undefined : moduleSpecifiers.getModuleSpecifier(program, sourceFile, newImportFromPath, toImport, host, preferences);
});
}
}
function combineNormal(pathA: string, pathB: string): string {
return normalizePath(combinePaths(pathA, pathB));
}
function combinePathsSafe(pathA: string, pathB: string): string {
return ensurePathIsNonModuleName(combineNormal(pathA, pathB));
}
function getSourceFileToImport(importLiteral: StringLiteralLike, importingSourceFile: SourceFile, program: Program, host: LanguageServiceHost, oldToNew: PathUpdater): string | undefined {
const symbol = program.getTypeChecker().getSymbolAtLocation(importLiteral);
if (symbol) {
if (symbol.declarations.some(d => isAmbientModule(d))) return undefined; // No need to update if it's an ambient module
const oldFileName = find(symbol.declarations, isSourceFile)!.fileName;
return oldToNew(oldFileName) || oldFileName;
}
else {
const resolved = host.resolveModuleNames
? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName)
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName);
return getSourceFileToImportFromResolved(resolved, oldToNew, program);
}
}
function getSourceFileToImportFromResolved(resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, program: Program): string | undefined {
return resolved && (
(resolved.resolvedModule && getIfInProgram(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, getIfInProgram));
function getIfInProgram(oldLocation: string): string | undefined {
const newLocation = oldToNew(oldLocation);
return program.getSourceFile(oldLocation) || newLocation !== undefined && program.getSourceFile(newLocation)
? newLocation || oldLocation
: undefined;
}
}
function updateImportsWorker(sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker, updateRef: (refText: string) => string | undefined, updateImport: (importLiteral: StringLiteralLike) => string | undefined) {
for (const ref of sourceFile.referencedFiles) {
const updated = updateRef(ref.fileName);
if (updated !== undefined && updated !== sourceFile.text.slice(ref.pos, ref.end)) changeTracker.replaceRangeWithText(sourceFile, ref, updated);
}
for (const importStringLiteral of sourceFile.imports) {
const updated = updateImport(importStringLiteral);
if (updated !== undefined && updated !== importStringLiteral.text) changeTracker.replaceRangeWithText(sourceFile, createStringRange(importStringLiteral, sourceFile), updated);
}
}
function createStringRange(node: StringLiteralLike, sourceFile: SourceFileLike): TextRange {
return createTextRange(node.getStart(sourceFile) + 1, node.end - 1);
}
function forEachProperty(objectLiteral: Expression, cb: (property: PropertyAssignment, propertyName: string) => void) {
if (!isObjectLiteralExpression(objectLiteral)) return;
for (const property of objectLiteral.properties) {
if (isPropertyAssignment(property) && isStringLiteral(property.name)) {
cb(property, property.name.text);
}
}
}
}
+2 -2
View File
@@ -1985,8 +1985,8 @@ 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 getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = defaultPreferences): ReadonlyArray<FileTextChanges> {
return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences);
}
function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
+1 -1
View File
@@ -496,7 +496,7 @@ namespace ts.textChanges {
else if (isStatement(node) || isClassOrTypeElement(node)) {
return { suffix: this.newLineCharacter };
}
else if (isVariableDeclaration(node)) {
else if (isVariableDeclaration(node) || isStringLiteral(node)) {
return { prefix: ", " };
}
else if (isPropertyAssignment(node)) {
+1 -1
View File
@@ -342,7 +342,7 @@ namespace ts {
getApplicableRefactors(fileName: string, positionOrRange: 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>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
+1 -1
View File
@@ -4564,7 +4564,7 @@ declare namespace ts {
getApplicableRefactors(fileName: string, positionOrRange: 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>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getProgram(): Program;
dispose(): void;
+1 -1
View File
@@ -4564,7 +4564,7 @@ declare namespace ts {
getApplicableRefactors(fileName: string, positionOrRange: 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>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getProgram(): Program;
dispose(): void;
@@ -14,8 +14,11 @@
/////// <reference path="../old.ts" />
////import old from "../old";
// @Filename: /src/new.ts
////
// @Filename: /tsconfig.json
////{ "files": ["/a.ts", "/src/a.ts", "/src/foo/a.ts", "/src/old.ts"] }
////{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/old.ts"] }
verify.getEditsForFileRename({
oldPath: "/src/old.ts",
@@ -24,6 +27,6 @@ verify.getEditsForFileRename({
"/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";',
"/tsconfig.json": '{ "files": ["/a.ts", "/src/a.ts", "/src/foo/a.ts", "/src/new.ts"] }',
"/tsconfig.json": '{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/new.ts"] }',
},
});
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
// @moduleResolution: classic
// @Filename: /src/user.ts
////import { x } from "old";
// @Filename: /src/old.ts
////
verify.getEditsForFileRename({
oldPath: "/src/old.ts",
newPath: "/src/new.ts",
newFileContents: {
"/src/user.ts":
`import { x } from "./new";`,
},
});
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
////export const a = 0;
// @Filename: /b.ts
////import { a } from "./A";
verify.getEditsForFileRename({
oldPath: "/a.ts",
newPath: "/eh.ts",
newFileContents: {
"/b.ts": 'import { a } from "./eh";',
},
});
@@ -0,0 +1,61 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////// <reference path="./src/old/file.ts" />
////import old from "./src/old";
////import old2 from "./src/old/file";
////export default 0;
// @Filename: /src/b.ts
/////// <reference path="./old/file.ts" />
////import old from "./old";
////import old2 from "./old/file";
////export default 0;
// @Filename: /src/foo/c.ts
/////// <reference path="../old/file.ts" />
////import old from "../old";
////import old2 from "../old/file";
////export default 0;
// @Filename: /src/new/index.ts
////import a from "../../a";
////import a2 from "../b";
////import a3 from "../foo/c";
////import f from "./file";
////export default 0;
// @Filename: /src/new/file.ts
////
// @Filename: /tsconfig.json
////{ "files": ["a.ts", "src/b.ts", "src/foo/c.ts", "src/old/index.ts", "src/old/file.ts"] }
verify.getEditsForFileRename({
oldPath: "/src/old",
newPath: "/src/new",
newFileContents: {
"/a.ts":
`/// <reference path="./src/new/file.ts" />
import old from "./src/new";
import old2 from "./src/new/file";
export default 0;`,
"/src/b.ts":
`/// <reference path="./new/file.ts" />
import old from "./new";
import old2 from "./new/file";
export default 0;`,
"/src/foo/c.ts":
`/// <reference path="../new/file.ts" />
import old from "../new";
import old2 from "../new/file";
export default 0;`,
// No change to /src/new
"/tsconfig.json":
`{ "files": ["a.ts", "src/b.ts", "src/foo/c.ts", "src/new/index.ts", "src/new/file.ts"] }`,
},
});
@@ -0,0 +1,66 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////// <reference path="./src/old/file.ts" />
////import old from "./src/old";
////import old2 from "./src/old/file";
////export default 0;
// @Filename: /src/b.ts
/////// <reference path="./old/file.ts" />
////import old from "./old";
////import old2 from "./old/file";
////export default 0;
// @Filename: /src/foo/c.ts
/////// <reference path="../old/file.ts" />
////import old from "../old";
////import old2 from "../old/file";
////export default 0;
// @Filename: /src/newDir/new/index.ts
////import a from "../../a";
////import a2 from "../b";
////import a3 from "../foo/c";
////import f from "./file";
////export default 0;
// @Filename: /src/newDir/new/file.ts
////
// @Filename: /tsconfig.json
////{ "files": ["a.ts", "src/b.ts", "src/foo/c.ts", "src/old/index.ts", "src/old/file.ts"] }
verify.getEditsForFileRename({
oldPath: "/src/old",
newPath: "/src/newDir/new",
newFileContents: {
"/a.ts":
`/// <reference path="./src/newDir/new/file.ts" />
import old from "./src/newDir/new";
import old2 from "./src/newDir/new/file";
export default 0;`,
"/src/b.ts":
`/// <reference path="./newDir/new/file.ts" />
import old from "./newDir/new";
import old2 from "./newDir/new/file";
export default 0;`,
"/src/foo/c.ts":
`/// <reference path="../newDir/new/file.ts" />
import old from "../newDir/new";
import old2 from "../newDir/new/file";
export default 0;`,
"/src/newDir/new/index.ts":
`import a from "../../../a";
import a2 from "../../b";
import a3 from "../../foo/c";
import f from "./file";
export default 0;`,
"/tsconfig.json":
`{ "files": ["a.ts", "src/b.ts", "src/foo/c.ts", "src/newDir/new/index.ts", "src/newDir/new/file.ts"] }`,
},
});
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
// @Filename: /a/b/file1.ts
////import { foo } from "foo";
// @Filename: /a/b/node_modules/foo/index.d.ts
////export const foo = 0;
verify.getEditsForFileRename({
oldPath: "/a/b",
newPath: "/a/d",
newFileContents: {},
});
@@ -0,0 +1,66 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////// <reference path="./src/old/file.ts" />
////import old from "./src/old";
////import old2 from "./src/old/file";
////export default 0;
// @Filename: /src/b.ts
/////// <reference path="./old/file.ts" />
////import old from "./old";
////import old2 from "./old/file";
////export default 0;
// @Filename: /src/foo/c.ts
/////// <reference path="../old/file.ts" />
////import old from "../old";
////import old2 from "../old/file";
////export default 0;
// @Filename: /newDir/new/index.ts
////import a from "../../a";
////import a2 from "../b";
////import a3 from "../foo/c";
////import f from "./file";
////export default 0;
// @Filename: /newDir/new/file.ts
////
// @Filename: /tsconfig.json
////{ "files": ["a.ts", "src/b.ts", "src/foo/c.ts", "src/old/index.ts", "src/old/file.ts"] }
verify.getEditsForFileRename({
oldPath: "/src/old",
newPath: "/newDir/new",
newFileContents: {
"/a.ts":
`/// <reference path="./newDir/new/file.ts" />
import old from "./newDir/new";
import old2 from "./newDir/new/file";
export default 0;`,
"/src/b.ts":
`/// <reference path="../newDir/new/file.ts" />
import old from "../newDir/new";
import old2 from "../newDir/new/file";
export default 0;`,
"/src/foo/c.ts":
`/// <reference path="../../newDir/new/file.ts" />
import old from "../../newDir/new";
import old2 from "../../newDir/new/file";
export default 0;`,
"/newDir/new/index.ts":
`import a from "../../a";
import a2 from "../../src/b";
import a3 from "../../src/foo/c";
import f from "./file";
export default 0;`,
"/tsconfig.json":
`{ "files": ["a.ts", "src/b.ts", "src/foo/c.ts", "newDir/new/index.ts", "newDir/new/file.ts"] }`,
},
});
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
// @allowJs: true
// @Filename: /src/a.js
////export const a = 0;
// @Filename: /b.js
////import { a } from "./src/a.js";
verify.getEditsForFileRename({
oldPath: "/b.js",
newPath: "/src/b.js",
newFileContents: {
"/b.js":
`import { a } from "./a.js";`,
},
});
@@ -18,7 +18,7 @@
////import old from "../old";
// @Filename: /tsconfig.json
////{ "files": ["/a.ts", "/src/a.ts", "/src/foo/a.ts", "/src/old.ts"] }
////{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/old.ts"] }
verify.getEditsForFileRename({
oldPath: "/src/old.ts",
@@ -27,6 +27,6 @@ verify.getEditsForFileRename({
"/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";',
"/tsconfig.json": '{ "files": ["/a.ts", "/src/a.ts", "/src/foo/a.ts", "/src/new.ts"] }',
"/tsconfig.json": '{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/new.ts"] }',
},
});
@@ -0,0 +1,43 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////// <reference path="./src/index.ts" />
////import old from "./src";
////import old2 from "./src/index";
// @Filename: /src/a.ts
/////// <reference path="./index.ts" />
////import old from ".";
////import old2 from "./index";
// @Filename: /src/foo/a.ts
/////// <reference path="../index.ts" />
////import old from "..";
////import old2 from "../index";
// @Filename: /src/index.ts
////
// @Filename: /tsconfig.json
////{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/index.ts"] }
verify.getEditsForFileRename({
oldPath: "/src/index.ts",
newPath: "/src/new.ts",
newFileContents: {
"/a.ts":
`/// <reference path="./src/new.ts" />
import old from "./src/new";
import old2 from "./src/new";`,
"/src/a.ts":
`/// <reference path="./new.ts" />
import old from "./new";
import old2 from "./new";`,
"/src/foo/a.ts":
`/// <reference path="../new.ts" />
import old from "../new";
import old2 from "../new";`,
"/tsconfig.json":
'{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/new.ts"] }',
},
});
@@ -0,0 +1,37 @@
/// <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";
// @Filename: /src/old.ts
////
// @Filename: /tsconfig.json
////{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/old.ts"] }
verify.getEditsForFileRename({
oldPath: "/src/old.ts",
newPath: "/src/index.ts",
newFileContents: {
"/a.ts":
`/// <reference path="./src/index.ts" />
import old from "./src";`,
"/src/a.ts":
`/// <reference path="./index.ts" />
import old from ".";`,
"/src/foo/a.ts":
`/// <reference path="../index.ts" />
import old from "..";`,
"/tsconfig.json":
'{ "files": ["a.ts", "src/a.ts", "src/foo/a.ts", "src/index.ts"] }',
},
});
@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
// @Filename: /src/foo/x.ts
////
// @Filename: /src/foo/new.ts
////import { x } from "./foo/x";
verify.getEditsForFileRename({
oldPath: "/src/old.ts",
newPath: "/src/foo/new.ts",
newFileContents: {
"/src/foo/new.ts":
`import { x } from "./x";`,
},
});
@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
// @Filename: /src/foo/a.ts
////
// @Filename: /src/dir/new.ts
////import a from "./foo/a";
verify.getEditsForFileRename({
oldPath: "/src/old.ts",
newPath: "/src/dir/new.ts",
newFileContents: {
"/src/dir/new.ts":
`import a from "../foo/a";`,
},
});
@@ -0,0 +1,41 @@
/// <reference path='fourslash.ts' />
// @Filename: /src/tsconfig.json
////{
//// "compilerOptions": {
//// "baseUrl": "./old",
//// "mapRoot": "../src/old",
//// "paths": {
//// "foo": ["old"],
//// },
//// "rootDir": "old",
//// "rootDirs": ["old"],
//// "typeRoots": ["old"],
//// },
//// "files": ["old/a.ts"],
//// "include": ["old/*.ts"],
//// "exclude": ["old"],
////}
verify.getEditsForFileRename({
oldPath: "/src/old",
newPath: "/src/new",
newFileContents: {
"/src/tsconfig.json":
`{
"compilerOptions": {
"baseUrl": "new",
"mapRoot": "new",
"paths": {
"foo": ["new"],
},
"rootDir": "new",
"rootDirs": ["new"],
"typeRoots": ["new"],
},
"files": ["new/a.ts"],
"include": ["new/*.ts"],
"exclude": ["new"],
}`,
},
});
@@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />
// @Filename: /src/tsconfig.json
////{
//// "include": ["dir"],
////}
verify.getEditsForFileRename({
oldPath: "/src/dir/a.ts",
newPath: "/src/newDir/b.ts",
newFileContents: {
"/src/tsconfig.json":
`{
"include": ["dir", "newDir/b.ts"],
}`,
},
});
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts' />
// @Filename: /src/tsconfig.json
////{
//// "include": ["dir"],
////}
verify.getEditsForFileRename({
oldPath: "/src/dir/a.ts",
newPath: "/src/dir/b.ts",
newFileContents: {},
});