An emacs macro to transform DTOs

TBH I just feel cool whenever I make a macro that works 😎

I really like how emacs macros let me capture and replay my own stupid approaches to problems.

I had a bunch of DTO classes that looked like this:

public class ThingContainer
{
    [JsonPropertyName("thing_one")]
    public string ThingOne { get; set; }

    [JsonPropertyName("thing_two")]
    public string ThingTwo { get; set; }
}

and I wanted to convert them into records like this:

public sealed record ThingContainer(
    [property: JsonPropertyName("thing_one")] string ThingOne,
    [property: JsonPropertyName("thing_two")] string ThingTwo);

I'm sure Visual Studio or Rider has a feature for doing that, but I am just a simple country emacs user so I used some find and replace commands, and made a macro so it was easy to repeat. This is all using evil mode and some very vim commands.

First off, change class to sealed record:

:%s/class/sealed record

This is a change that was easy enough to apply to all the classes at once, so it doesn't need to be part of a macro.

Now to the fiddly bit, position the cursor on the opening curly boi and start a macro (C-x ( in my config) then do the following:

Select the whole body of the class

V %

Add the property prefix to the attributes:

:'<,'>s/JsonProperty/property: JsonProperty/

After each find/replace command the text is unselected so it needs to be selected again before running the next one. In this case, the cursor ends up on the last attribute, so moving down twice gets to the closing curly boi (j j) then selecting everything is the same as before:

V %

Remove public from all the properties:

:'<,'>s/public //

Select the whole class again by moving one line down j and then pressing V %

Remove the get and set, and the blank line as well:

:'<,'>s/ { get.*\n/,/

Select the whole class again by pressing V %

Remove the newline between the attributes and the properties:

:'<,'>s/]\n */] /

Move to the beginning of the class A to get to the end of the line % to jump to the opening curly boi

A to edit at the end of the line, then backspace twice to delete the curly boi and newline, replace it with (, esc to go back to normal mode after typing.

This change breaks the scope declaration so % won't work for getting to the end of the block anymore, instead use } to get to the next blank line, and b jumps backwards to the trailing comma. Select that and the closing curly boi with v e then c to replace it, type ); as the last part of our record, then esc for back to normal mode.

Finally C-x ) to finish recording the macro.

Now it can be used on all the other classes by moving the cursor to the right position and hitting C-x e

Doing it this way let me work through the changes I wanted to make one at a time, and then have a simple command for all the other classes where I wanted to make the change 😃