Sourceror - Utilities to work with Elixir source code

Sourceror 0.8.0 is out :slight_smile:

This one is a bit small, just bug fixes and the addition of Sourceror.patch_string/2 which allows you to modify just some parts of a string, instead of having to print the whole tree. It receives the original string and a list of patches to be applied.

A patch is just a map with a :range pointing to the start and end positions to be replaced, and a :change that can be either a string, in which case the range will be replaced with it, or a function that accepts the original code in that range, and returns the string that replaces it.

This allows you to perform more fine grained changes to the source code, as you can use Sourceror.get_range/1 in combination with a traversal to generate the patches.

Also, now Sourceror.to_string/2 can receive a format: :splicing option that makes it easier to print elements of a keyword list without having to string the brackets yourself.

To illustrate these changes, here’s how a transformation for the Surface converter that renames the slot props: ... to args: ... would look like (from a discussion in the issue tracker):

Mix.install([{:sourceror, "~> 0.8"}])

code = """
defmodule Card do
  use Surface.Component

  slot footer
  slot header, props: [:item]
  slot default, required: true, props: [:item]
end
"""

{_, patches} =
  code
  |> Sourceror.parse_string!()
  |> Sourceror.postwalk([], fn
    {:slot, _, args} = quoted, state ->
      opts_node = Enum.at(args, 1, [])
      props_node = Enum.find(opts_node, &match?({{:__block__, _, [:props]}, _}, &1))

      if props_node do
        range = Sourceror.get_range(props_node)
        {{:__block__, meta, [:props]}, body} = props_node
        args_node = {{:__block__, meta, [:args]}, body}
        new_code = Sourceror.to_string([args_node], format: :splicing)
        patch = %{change: new_code, range: range}

        {quoted, %{state | acc: [patch | state.acc]}}
      else
        {quoted, state}
      end

    quoted, state ->
      {quoted, state}
  end)

code
|> Sourceror.patch_string(patches)
|> IO.puts

The next versions will be focused on exploring ways to make it easier to create the patches, and to make the apis more stable :slight_smile:

Changelog:

1. Enhancements

  • [Sourceror] Added Sourceror.patch_string/2
  • [Sourceror] Added the format: :splicing option to Sourceror.to_string/2

2. Bug fixes

  • [Sourceror] Now Sourceror.to_string/2 won’t produce invalid Elixir code when a keyword list element is at the beginning of a non-keyword list.
  • [Sourceror] Now Sourceror.get_range/1 will take the leading comments into account when calculating the range.
6 Likes