This is the list of our refactors TSServer offers and the proposed changes to make them more discoverable. See #34930 for the broader discussion.
Here I have listed the current behavior as well as proposed changes to make the refactors more discoverable. Some of these ideas may offer refactors too frequently/infrequently, so more input is desired. The explicit refactoring request cases mentioned depend on #35096.
This issue does not list any code fixes.
Case - remove braces:
const a = (n: number) => {
return n;
}
Current:
(n: number). selection end can be anywhere else.Proposed:
Case - Convert default export to named export:
export default function f();
Case - Convert named export to default:
export function f();
Current:
Proposed:
f() or default.return statement, so we aren't in danger of offering up this refactor too much. We may even be fine offering it as often as in the explicit case.Case - Convert namespace import to named imports:
import * as a from "./a";
Case - Convert named import to namespace import:
import { f } from "./a";
Current:
Proposed:
* as a or { f }.Case - extract const
function foo(a: any) {
return a + 20 * 10;
}
Current:
2 offers to extract 20. This is not the case for other valid extraction candidates such as Identifiers or functions.20 + 1 is selected, no refactor is offered.Proposed:
Case - extract method
function foo(){
function bar(){
return 0;
}
}
Current
Proposed:
function keyword (or both).function bar.Case - extract to type alias
var y: string = '';
Current:
string selected.Proposed:
string as the selecting whole type.stringCase - extract to interface
var x: { a?: number, b?: string } = { };
Current:
{ a?: number, b?: string } selected.Proposed:
Case - Generate 'get' and 'set' accessors
class A {
public convertMe: string;
}
Current:
convertMe.Proposed:
Included for completeness. These cases are probably fine as is.
function f(a: number, b: number) {
return a + b;
}
Current:
function f(a: number, b: number. Selection end can be anywhere else.const a = "as above, ";
const b = a + "so below";
Current:
a + "so below. Selection end can be anywhere else.console.log("goodbye");
Current:
This list and the expected behavior look good to me. Thanks for putting it together!
Hello 馃憢
I was curious about this feature because tide only offers a single tide-refactor function.
It doesn't have much documentation on what it does and I only recently realized it is calling tsserver getApplicableRefactorings internally.
I tried looking around the documentation here and only through these issues did I figure out how this stuff works on the tsserver side (One has to select a chunk of text to be sent to tsserver to get the refactoring candidates available in that position)
Is there a list of the available refactorings somewhere or some documentation on them? I'm interested in improving the UX for this stuff on the tide side but am having trouble figuring out how it all works on the tsserver side. It'd be cool to have a list of the refactoring capabilities supported by tsserver.
Btw, thanks a lot for the work on all of this. It seems almost magical 馃槏
(sorry if this isn't the right place to ask this but it seemed related to "refactor discoverability 馃檲 )
Refactors live here:
https://github.com/microsoft/TypeScript/tree/master/src/services/refactors
There has not been great documentation for available refactors and code fixes in the past, but we're working on changing that. I think the best we have at the moment is https://github.com/microsoft/JSTSdocs/blob/master/articles/editor/refactoring.md.
To get an idea of how they work, I found it helpful to set a breakpoint in the function that gets the available refactors for a given span, for example getImportToConvert in convertImport.ts, and note that it will be called twice over the lifetime of a refactor operation.
An editor should first send getApplicableRefactors to get a list of available refactors by checking each refactor to see which are applicable to the requested span. When a user selects one of the offered refactors, the editor would then want to send getEditsForRefactor with the selected refactor. tsserver will then match the requested refactor with one from the applicable refactors list and perform the refactor.
Thank you for the quick reply @jessetrinity ! That is useful 馃憤
I was wondering because as it currently stands the UX of these refactorings seem a bit hard to discover naturally while coding. Right now, you just select a bunch of code randomly and see what comes up as a reply from getApplicableRefactors (at least on the emacs world, don't know how it is used on vscode). Maybe just pointing users to the doc could be good enough.
Yeah, having to select the exact span does make them hard to discover. VS and VSCode have an icon that pops up when a span is selected to indicate that a refactor is available. The effort associated with this issue is returning the refactors if the span is not exactly selected, or if the cursor is simply within a valid span when getApplicableRefactors is sent. Adding more docs is a good idea too, thanks for that input!
I'm only vaguely familiar with how this looks in emacs. Is requesting available refactors at a cursor location something that would fit into the workflow for emacs users (is there a default key command to send the request)?
The idea is that, in VSCode for example, using the refactor shortcut ctrl + . when the cursor is at /**/ in the following would return the extractSymbol refactor for 100 + 200.
function foo(){
return 1/**/00 + 200;
}
Closing following #38378 which added the refactor trigger reason and expanding the spans for some refactors. If we want to tweak any of the spans further we can open items for those refactors.
Most helpful comment
Refactors live here:
https://github.com/microsoft/TypeScript/tree/master/src/services/refactors
There has not been great documentation for available refactors and code fixes in the past, but we're working on changing that. I think the best we have at the moment is https://github.com/microsoft/JSTSdocs/blob/master/articles/editor/refactoring.md.
To get an idea of how they work, I found it helpful to set a breakpoint in the function that gets the available refactors for a given span, for example
getImportToConvertin convertImport.ts, and note that it will be called twice over the lifetime of a refactor operation.An editor should first send
getApplicableRefactorsto get a list of available refactors by checking each refactor to see which are applicable to the requested span. When a user selects one of the offered refactors, the editor would then want to sendgetEditsForRefactorwith the selected refactor. tsserver will then match the requested refactor with one from the applicable refactors list and perform the refactor.