mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
* 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:
@@ -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
@@ -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].
|
||||
|
||||
@@ -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
@@ -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> {
|
||||
|
||||
@@ -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: [{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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: {},
|
||||
});
|
||||
Reference in New Issue
Block a user