I go through bouts of playing around with re-writing source-code using Sourceror. It generally goes: I learn a whole bunch, start getting comfortable with it, stop doing it for many months and forget everything
Iām back at it and just finished creating a function that will inline single pipes. IE:
socket
|> assign(:foo, "foo")
becomes:
assign(socket, :foo, "foo")
I was looking to get some feedback on my solution.
I initially thought I could get away with simply pre- or postwalking with some clever pattern-matching, but that proved to be difficult for me. I ended up using a zipper which made life a LOT easier getting me to a solution quite quickly. There are still a few issues with line-length but Iām otherwise quite happy with the clarity of it.
Still, Iām wondering:
Is this possible using walking with an accumulator?
Do you have a solution thatās different/better than mine?
Yes, any single pipes, though that particular case I did not account for! It does indeed work here though it adds new lines (which is a separate general issue Iām dealing with).
These were my test cases so far (not super thorough):
defp run(string, func) do
result =
string
|> Sourceror.parse_string!()
|> func.()
|> Sourceror.to_string()
result <> "\n"
end
describe "unpipe" do
test "changes single pipes into inline calls" do
source = ~S"""
socket
|> assign(:foo, "foo")
"""
result = run(source, &unpipe/1)
assert result == ~S"""
assign(socket, :foo, "foo")
"""
end
test "leaves longer pipelines alone" do
source = ~S"""
socket
|> assign(:foo, "foo")
|> assign(:bar, "bar")
"""
result = run(source, &unpipe/1)
assert result == ~S"""
socket
|> assign(:foo, "foo")
|> assign(:bar, "bar")
"""
end
test "handles nested pipes" do
source = ~S"""
foo
|> bar()
|> baz(fn n ->
n
|> bar()
|> baz(fn m ->
m
|> foo()
end)
end)
"""
result = run(source, &unpipe/1)
assert result == ~S"""
foo
|> bar()
|> baz(fn n ->
n
|> bar()
|> baz(fn m ->
foo(m)
end)
end)
"""
end
Ahhh, interesting. Only pipes where there isnāt a 2 or more pipes. Neat idea In that case your traverse + prev/next strategy looks like the best way to go about this IMO. Itās a benefit of Zipper that you actually donāt need an accumulator to do this kind of thing. It would only add complexity IMO.
Yes, I just started on a new team that inherited a codebase where itās rampant. Iām not too fussed about āno single pipesā depending on how they are used, but they are everywhere to point itās making code exhausting to read. There is even stuff like this:
"string"
|> String.capitalize()
In any event, weāre getting all of our bikeshedding out of the way upfront And itās also nice to have a task to run in CI.
Thatās good to know! Zipper certainly allowed me to write code inline with how I was thinking about the problem. Itās more of an academic interest if itās even possible with basic walking as it was hurting my brain trying to figure it out. Iām happy to move on from it, though.
The problem is is that itās doing it for any function calls that have more than one arg, even if the line is within the limit. I believe it has to do with how Sourceror adds :__block__ around stuff to help maintain the original format. Iām pretty sure I just need to be more explicit in how I return the transformed node. I believe the answer might be in here somewhere.
Ah, right. I think what you can do is check if the node previous to the top node is {:__block__, meta, [just_one_thing]} and if so, replace that node instead? Might mess w/ your traversal though.
Iāve looked quickly at igniter but havenāt tried it out yet. Over a year ago now I started a very similar project called vials, though once it got to where I was happy with it. I went from the perspective of āwrappingā mix tasks though really itās just for editing generators and really it was just about editing the output of mix phx.new. Overall, it felt a bit unfocused so Iāll check it out. I do remember hearing about a similar project also about a year agoā¦ was that you? Were you talking about the beginnings of igniter back then?
I am aware of Styler but itās too stringent for our tastes Weāre small team and not looking for enforcement, just to fix a whole bunch of stuff that we considered poor/inconsistent style.
Iāve talked about the idea itself many times in the past, but it was pretty nebulous. I donāt think that igniter is really what youāre looking for, just perhaps good for some inspiration potentially
Thatās good to know! Iāve been thinking of polishing and focusing up that package for a while now, even just for learnings, but have been spending my time on other ventures. I made it at a time where I was prototyping a new Phoenix app weekly and I donāt do that anymore. I will certainly look to igniter for inspiration, though. I remember a convo where you and Ben were talking about using closures in module attributes which was the big piece of refactoring I wanted to do, even just to see it work. Iām currently using a GenServer to get around not being able to figure that out for myself but anyway, that is a bit off topic