Exploring advanced refactoring/code actions features

One of the things that really set ReScript apart from the alternatives is its incredible type system. The characteristics of it feels like it should lend itself well to advanced refactoring features that would be hard or impossible in other, weaker type systems.

Me and @cristianoc have been chatting a bit about this and related topics, and it would be very interesting for us as a community to start throwing ideas around for refactoring features that can be explored.

So, let us throw some ideas around for refactoring features that could be helpful for our day to day work using ReScript! I suggest we try and focus on features that’d be as impactful as possible, but any idea is of course welcome, small as large. This is a really interesting space, and I’m sure we as a community can come up with a ton of interesting features that can eventually be considered for implementation.

There’s some prior art from @cristianoc here about refactoring record properties to be optional from non-optional that’s a nice inspiration: Explore advanced refactoring features. · Issue #243 · rescript-lang/rescript-vscode · GitHub

3 Likes

I’ll go first! :grinning_face_with_smiling_eyes:

Adding member to variant

Add a new member to a variant, and have it automatically explicitly matched on in every pattern match that targets it.

This would be handy when extending variants, and would make it easy to walk through all pattern matches and adapt as needed.

// Before
type someVariant = FirstMember | SecondMember(int)

let someFunc = value => switch value {
  | FirstMember => "Hello!"
  | SecondMember(someInt) => "Hello " ++ string_of_int(someInt)
}
// After doing "insert new variant member"
type someVariant = FirstMember | SecondMember(int) | ThirdMember

let someFunc = value => switch value {
  | FirstMember => "Hello!"
  | SecondMember(someInt) => "Hello " ++ string_of_int(someInt)
  | ThirdMember => assert false // TODO_REFACTOR
}
4 Likes

Remove variant member

Removing a variant member, including all pattern matches for it. Same rationale as adding a new variant member - removes a bunch of manual work in removing pattern matches.

// Before
type someVariant = FirstMember | SecondMember(int)

let someFunc = value => switch value {
  | FirstMember => "Hello!"
  | SecondMember(someInt) => "Hello " ++ string_of_int(someInt)
}
// After doing "remove variant member" on SecondMember
type someVariant = FirstMember

let someFunc = value => switch value {
  | FirstMember => "Hello!"
}
4 Likes

A note about refactoring with placeholders.
I don’t like automatic adding of patterns on several places since you can easily ignore those patterns (a todo comment is not useful here IMO).
I prefer having an action (codelens action in vscode) on non exhaustive warnings saying “hey this pattern matching is not exhaustive do you want me to add missing patterns”?

7 Likes

Great topic! I’d like “simple” things like renaming to work across a project (the one recently added in the vs code extension is not always working for me).

Random ideas for some vs code refactoring actions:

  • Rewrite if / else to a switch and maybe the other way around as well.
  • Add inferred type to the code on fields, functions etc…
  • Add missing branches in a switch.
  • Delete refactoring to remove all usages of a field / Module / etc…

Maybe an advanced feature could be to identify all the types / records / objects with a similar structure. I sometimes find types in different places with the same content but different names. Not a big problem, but could be useful to stay DRY.

2 Likes

I wonder if automatically removing variants from pattern matching is harmless. Your example is trivial, but what if some of those matches will actually show you how that variant was useful, or will require some thoughtful refactoring on the consuming side?

4 Likes

Great feedback!

Yeah I can agree on that for sure, although I see them as distinct/separate features. One wouldn’t need to exclude the other, and in my style of development I can see me using both quite extensively.

I’m not quite sure I follow what you mean, could you perhaps examplify? Also note that one could of course simply decide to not use the refactoring functionality, and instead just remove the variant member manually. This would be for the cases when it does make sense to remove all of the matches.

1 Like

I really like this one! In combination with having a code action for adding a switch for an option etc, this would be quite a nice experience I think.

1 Like

The thing I would appreciate being automated is module refactoring when you want to move a submodule to its own file or the other way around when you want to move a file-level module to a submodule in some other file. I’m pretty sure this is “easily” doable.

5 Likes

my wishlist:

  1. convert array to list or list to array
let ids = list{1,2,3} // Click convert to array results in let ids = [1,2,3]
  1. extract code block into function
  2. extract selected JSX into component
3 Likes