-
Notifications
You must be signed in to change notification settings - Fork 13.2k
'Move to file' refactor #53542
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
'Move to file' refactor #53542
Conversation
|
Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @mjbvz, @zkat, and @joj for you. Feel free to loop in other consumers/maintainers if necessary |
mjbvz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The protocol parts of this change look good to me. Excited to start testing this in VS Code
src/server/protocol.ts
Outdated
| /* The 'name' property from the refactoring that offered this action */ | ||
| refactor: string; | ||
| /* The 'name' property from the refactoring action */ | ||
| action: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the refactor and action properties needed? Or can we assume they are Move to file since we're making this call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I believe the refactor and the action properties are needed to compute the associated code action. getEditsForAction in types.ts matches the action property to the correct refactor action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this new API though, won't the action always be Move to file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I said today that my API questions didn’t affect the protocol, but I was kind of wrong; there is a parallel. My suggestion on the LS side was to remove the move-to-file-specific LS method and allow an extra argument to be passed to the existing getEditsForRefactor method. The exact same possibility exists here: instead of making a new command, we could reuse the existing GetEditsForRefactor command if we modify its request type to allow an argument containing the targetFile property. @mjbvz thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good with that. For the protocol, I'll just need the expected argument's type
|
Testing this out in VS Code, a few small issues I noticed so far:
|
|
@navya9singh microsoft/vscode#178535 Hooks up initial support for this refactoring on the VS Code side. I'm hoping to land it in the first VS code 1.78 insiders that rolls out later this week. Should make testing easier. Please also send me any feedback on the UI |
Add initial support for move to file Fixes #176705 For microsoft/TypeScript#53542
|
It looks like quite a bit of code is copied and pasted from |
@andrewbranch Can the duplicated functions be places in a file under |
src/services/services.ts
Outdated
| function getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions): { newFileName: string | undefined, files: string[] | undefined } { | ||
| synchronizeHostData(); | ||
| const sourceFile = getValidSourceFile(fileName); | ||
| const program = getProgram(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
program is already available to you without declaring this constant; see the other functions in this file as an example. This is similar to other places where I mentioned that code was unnecessarily defensive—if you find yourself with a type that says it could be undefined, but your function can’t do anything useful if that object is undefined, it’s worth figuring out whether it can actually be undefined in reality. Often, the answer is that if you have an undefined, some core invariant has already been violated, and it’s better to throw than silently do nothing. Presenting the user with an empty list of files isn’t going to help the user—they’re better off seeing VS Code present them an error message saying that something went wrong. And when that user files a bug, you’ll be very happy that they were able to paste a stack trace to exactly where something went wrong instead of just saying “I tried to move this code to another file and nothing happened 🤷”
src/services/services.ts
Outdated
| const allFiles = program?.getSourceFiles().filter(sourceFile => !program?.isSourceFileFromExternalLibrary(sourceFile)).map(f => f.fileName); | ||
| const extension = extensionFromPath(fileName); | ||
| const files: string[] = []; | ||
| if (allFiles) { | ||
| for (const file of allFiles) { | ||
| const fileExtension = extensionFromPath(file); | ||
| if (sourceFile === getValidSourceFile(file) || extension === Extension.Ts && fileExtension === Extension.Dts || extension === Extension.Dts && startsWith(getBaseFileName(file), "lib.") && fileExtension === Extension.Dts) { | ||
| continue; | ||
| } | ||
| if (extension === fileExtension) { | ||
| files.push(file); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code iterates over the source files 3 times and creates and mutates a lot of intermediate arrays. It looks the filter, map, and for loop can be replaced with a single mapDefined call.
src/services/services.ts
Outdated
| } | ||
| } | ||
| } | ||
| //creating new filename |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| //creating new filename |
src/services/services.ts
Outdated
| let newFileName; | ||
| if (program) { | ||
| newFileName = createNewFileName(sourceFile, program, getRefactorContext(sourceFile, positionOrRange, preferences, emptyOptions), host); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t forget to simplify this after program is no longer typed as possibly undefined.
src/services/types.ts
Outdated
| applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>; | ||
|
|
||
| getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; | ||
| getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): { newFileName: string | undefined, files: string[] | undefined }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After fixing services.ts, can these return type properties ever actually be undefined?
|
#53915 can be incorporated now with something like this process:
|
| const aTs: File = { path: "/Foo/a.ts", content: "const x = 0;" }; | ||
| const bTs: File = { | ||
| path: "/Foo/b.ts", content: `import {} from "./bar"; | ||
| const a = 1;`}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of deleting this test, you can modify it to use the TS Server command you expect VS Code to use now. (In doing so, you’ll find that you need another update to protocol.ts to allow the interactive refactor arguments.)
andrewbranch
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! This was a long process; thanks for your patience over the many revisions. This will be an awesome feature 🙂
src/server/session.ts
Outdated
| const { newFileName, files } = project.getLanguageService().getMoveToRefactoringFileSuggestions(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file)); | ||
| return { newFileName, files }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const { newFileName, files } = project.getLanguageService().getMoveToRefactoringFileSuggestions(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file)); | |
| return { newFileName, files }; | |
| return project.getLanguageService().getMoveToRefactoringFileSuggestions(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file)); |
sheetalkamat
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from one nit it looks good to go
|
Thank you so much for working on this ❤️ |
|
Very nice feature, thanks for your work!! But this doesn't seem to behave correctly for overloads Given a function like function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any) {
return x + y;
}if you right click any of them and use this refactor, it will only move that part Code_-_Insiders_pnskO6qwK9.mp4Edit: I discovered it works correctly if you highlight all the functions and then use the refactor on the selection. But it would probably be better and more intuitive to properly handle overloads even if you don't? It works correctly for jsdoc at least, so I would expect it to also work for overloads The old "Move to a new file" also seems affected by this |
|
Folks, please create separate issues instead of leaving them on a closed PR. Thanks! |
Fixes #29988