Skip to content

getEditsForFileRename: Support directory rename#24305

Merged
17 commits merged intomasterfrom
getEditsForFileRename_directory
Jun 1, 2018
Merged

getEditsForFileRename: Support directory rename#24305
17 commits merged intomasterfrom
getEditsForFileRename_directory

Conversation

@ghost
Copy link
Copy Markdown

@ghost ghost commented May 21, 2018

Fixes #24260

@ghost ghost requested review from amcasey and sheetalkamat May 21, 2018 21:22
Comment thread src/services/getEditsForFileRename.ts Outdated
if (!program.getSourceFileFromReference(sourceFile, ref) && resolveTripleslashReference(ref.fileName, sourceFile.fileName) === oldFilePath) {
result.push({ sourceFile, toUpdate: ref });
}
if (program.getSourceFileFromReference(sourceFile, ref)) continue;
Copy link
Copy Markdown
Member

@sheetalkamat sheetalkamat May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would you want to skip if ref was resolved to some sourceFile?
if you had
index.ts:

/// <ref path="./file1.ts"/>

file1.ts

// Some content

You can rename file1.ts to file2.ts and want to change ref in index to file2.ts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I did a similar thing in #24328 but I'll have to update it in this PR too.

Comment thread src/services/getEditsForFileRename.ts Outdated
const rel = getRelativePathFromFile(oldFileOrDirPath, newFileOrDirPath, createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host)));
return (fullPath, relPath, isImport) => {
if (fullPath === oldFileOrDirPath) {
const newRelPath = pathIsRelative(relPath)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Pls rename relPath to say textInFile or something like that since it could be non relatvie?

Comment thread src/services/getEditsForFileRename.ts Outdated
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName);
if (resolved && contains(resolved.failedLookupLocations, oldFilePath)) {
result.push({ sourceFile, toUpdate: importStringLiteral });
const updated = resolved && firstDefined(resolved.resolvedModule ? [resolved.resolvedModule.resolvedFileName] : resolved.failedLookupLocations, path => pathUpdater(path, importStringLiteral.text, /*isImport*/ true));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to check failed lookup locations irrespective of if the resolvedFileName is present because those failedLookups take precedence over whatever was resolved ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it resolved successfully:

  • To something not in the old directory: We should leave it alone, right?
  • To something in the old directory: Then we'll recognize that at resolvedFileName and we're done with no need to continue on to failedLookupLocations, right?

Can you think of a case where this isn't sufficient?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it resolved to something not in oldDirectory, if it failed on something in old directory thats going to change the resolution no?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok ignore previous (i got confused between invalidation of resolution and change in name of the resolution), why would you want to change name of import if the changes are happening in file that's present in failed lookup locations of resolution

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's relevant if the rename has already happened and we've already updated the program. In that case there will be lots of unresolved imports that are trying to import from oldFile, but since that doesn't exist their resolution will fail.
So we have to look through their failed lookup locations to see if they could have resolved to oldFile if it still existed.
Note that means that we should only look at failed lookup locations if the import wasn't already successfully resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I have this structure:
Folders b and c are in a
There is node_modules in b where resolution in file a/b/file1.ts has failed lookup locations from a/b/node_modules/ because of say import {a} from "foo" You dont want to rename "foo" to something else right when renaming a/b to a/d ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newest commit should fix this, and #24345

@ghost ghost force-pushed the getEditsForFileRename_directory branch from 26c5d7b to 2f8edbd Compare May 23, 2018 18:52
Comment thread src/services/getEditsForFileRename.ts Outdated
// e.g., Importing from "/old/a/b", suffix is "/a/b", and we'll leave that part alone.
const relToOld = removeDirectoryPrefixFromPath(oldFileOrDirPath, fullPath);
if (relToOld === undefined) return undefined;
const suffix = isImport ? pathToImportPath(relToOld) : relToOld;
Copy link
Copy Markdown
Member

@sheetalkamat sheetalkamat May 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moving folder /a/b/ to folder say /a/c would need to move "resolvedFile name" of` import "foo" say ```/a/b/node_modules/foo/index.d.ts``` will make suffix as : ```"node_modules/foo"``` ? anmd newPrefix as ```"/a/c"``` and resulting import would be````"/a/c/node_modules/foo"``` ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function will only be called for imports from outside the moved directory, which won't resolve to node_modules in the moved directory unless it was an explicit relative import from "./b/node_modules/foo".

@ghost
Copy link
Copy Markdown
Author

ghost commented May 24, 2018

Note: filed #24384 for handling additional tsconfig settings.

Comment thread src/services/getEditsForFileRename.ts Outdated
return (sourceFile, importText) =>
!pathIsRelative(importText) ||
importIsInternalToDirectory(oldFileOrDirPath, sourceFile.fileName, importText) ||
importIsInternalToDirectory(newFileOrDirPath, sourceFile.fileName, importText)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If import text is relative and it was relative path resolving in newDirectory, you need to change the text right?
"/a/b/" to "/a/c" and my import text was "in/a/b/file1" was referring to "../c/file3" you want to change it to "./file3" ?
Would it be better to use getRelativePathToDirectoryOrUrl with file and reference new locations instead ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If /a/b is being renamed to /a/c, /a/c must not already exist -- so an import to ../c/file3 would have been an error before, right?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I see what you mean in the file case, see latest commit.

@mhegazy
Copy link
Copy Markdown
Contributor

mhegazy commented May 24, 2018

Please port to release-2.9 as well.

Comment thread src/services/getEditsForFileRename.ts Outdated
if (fullPath === oldFileOrDirPath) {
const newImportText = pathIsRelative(importText)
? combinePathsSafe(tryRemoveIndexOrPackage(fullPath) && !endsWithSomeIndex(importText) ? importText : getDirectoryPath(importText), rel)
: newFileOrDirPath;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With amd resolution, renaming /a/folder/oldModule.ts to /a/folder/newModule.ts and if I had file as

// /a/folder/someApp.ts 
import { a } from "oldModule"

to change to

// /a/folder/someApp.ts 
import { a } from "/a/folder/newModule"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #24385

Comment thread src/services/getEditsForFileRename.ts Outdated
}

function importIsInternalToDirectory(oldDir: string, sourceFilePath: string, importText: string): boolean {
return !!removeDirectoryPrefixFromPath(oldDir, combineNormal(getDirectoryPath(sourceFilePath), importText));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all these operations you want to take into account the case sensitivity as well (since you are using fileName instead of path)

@ghost
Copy link
Copy Markdown
Author

ghost commented May 24, 2018

Latest commit fixes #24385

@ghost ghost force-pushed the getEditsForFileRename_directory branch from 4f727db to 27f71fa Compare May 29, 2018 18:11
@ghost
Copy link
Copy Markdown
Author

ghost commented May 29, 2018

Latest commit fixes #24384

@ghost ghost force-pushed the getEditsForFileRename_directory branch from 8b39db7 to ccdd465 Compare May 30, 2018 20:40
@ghost
Copy link
Copy Markdown
Author

ghost commented May 30, 2018

Also fixes #24493

Comment thread src/services/getEditsForFileRename.ts Outdated
const configFile = program.getCompilerOptions().configFile;
/** 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): PathUpdater {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs to consider case-sensitive vs case-insensitive

if (!isPropertyAssignment(property) || !isStringLiteral(property.name)) continue;
switch (property.name.text) {
case "files":
case "include":
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

include could be "src/*.ts" and your old path will be oldFileOrDirPath will be full Path correct? (eg. /user/username/project/src). Don't you want to handle that?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are renaming file from "/projects/src/file1.ts" to "/projects/lib/file1.ts" and include is "src/*.ts" do you want to add "lib/file1.ts" in the include section ?

Comment thread src/services/getEditsForFileRename.ts Outdated
case "baseUrl":
case "typeRoots":
case "mapRoot":
case "rootDir":
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PathMapping needs to be updated too ?

@ghost ghost force-pushed the getEditsForFileRename_directory branch from 249888a to bc75e67 Compare May 31, 2018 19:56
Comment thread src/services/getEditsForFileRename.ts Outdated
case "typeRoots":
case "mapRoot":
case "rootDir":
case "rootDirs":
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except files, includes and exclude rest of these are in "compilerOptions" property. So you will miss those?

function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName): PathUpdater {
const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath);
return path => {
const canonicalPath = getCanonicalFileName(path);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still handles the path as is but you need to be able to handle include/exclude/file like:
"files": "src/foo.ts" or "./src/foo.ts", "../src/foo.ts" etc?

Copy link
Copy Markdown
Author

@ghost ghost May 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For include/exclude we don't just call this directly, see tryUpdateString. First we prepend the config directory to the path, call oldToNew, then convert to a relative path again.

Comment thread src/services/getEditsForFileRename.ts Outdated
case "mapRoot":
case "rootDir":
case "rootDirs":
updatePaths(property);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to use isFilePath from getOptionNameMap.get(propertyName) to update the paths? instead of listing all these here?

Copy link
Copy Markdown
Member

@sheetalkamat sheetalkamat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except suggestion to use getOptionNameMap().get().isFilePath changes look good.

@mhegazy
Copy link
Copy Markdown
Contributor

mhegazy commented May 31, 2018

@Andy-MS please port to release-2.9 as well.

@ghost ghost force-pushed the getEditsForFileRename_directory branch from 7457ef9 to 2a73698 Compare June 1, 2018 14:41
@ghost ghost merged commit d671c7a into master Jun 1, 2018
@ghost ghost deleted the getEditsForFileRename_directory branch June 1, 2018 15:23
ghost pushed a commit that referenced this pull request Jun 1, 2018
* 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
ghost pushed a commit that referenced this pull request Jun 1, 2018
* 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
@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants