dorgan

dorgan

Sourceror - Utilities to work with Elixir source code

Now that the next Elixir version will add Code.quoted_to_algebra/2 and Code.string_to_quoted_with_comments/2, we’re able to take some source code, parse it, change it and turn it back to formatted text. There are a couple gotchas if you change the ast in certain ways: since quoted_to_algebra requires the ast and comments to be given as separate arguments, we need to reconcile the line numbers of ast nodes and comments if we want the comments to be correctly placed.

So I wrote Sourceror, an (experimental) library that provides utilities to perform manipulations of the source code. I’m still working on more docs and examples(and tests), but this is an example of a function that expands multi alias syntax(ie: Foo.{Bar, Baz}) into their own lines:

Or a function to add a dependency to mix.exs ala npm install:

Since the new functions are only available in Elixir master, Sourceror depends on Elixir 1.13.0-dev and can only be installed via git dependency.

Most Liked

dorgan

dorgan

Sourceror is now available on hex.pm and supports Elixir versions down to 1.10 :slight_smile:

https://hexdocs.pm/sourceror/Sourceror.html

dorgan

dorgan

I finally got some time work on Sourceror, and there have been a bunch of bug fixes, and a new 0.9 version! Thanks to the folks that helped to test and iron out issues in both my comments syncer and Elixir 1.13 release candidate :slight_smile:

There are some slight changes to the API, and more functionality is exposed. Also, the notebooks are now available as guide pages in hexdocs.

v0.9.0

1. Enhancements

  • [Sourceror] to_string/2 now supports options for Code.quoted_to_algebra, like locals_without_parens
  • [Sourceror] get_range/2 no longer considers comments when calculating the range. This can be enabled by passing the include_comments: true option
  • [Sourceror.Patch] Introduced Sourceror.Patch with utilities to generate patches for the most common rewriting operations
  • [Sourceror.Identifier] Sourceror.Identifier is now public

v0.8.x summary of bug fixes

  • [Sourceror] Fixed comment spacing on binary operators
  • [Sourceror] Take comment end of line counts into account to preserve spacing
  • [Sourceror] Fixed an issue that caused comments in lists to be misplaced
  • [Sourceror] Fixed issues that caused comments to be misplaced.
  • [Sourceror] Updated internal normalizer to match latest Elixir 1.13 version.
  • [Sourceror] Fixed an issue that caused newlines to be wrongly removed.
  • [Sourceror] Fixed an issue that caused comments in pipelines to be misplaced.
  • [Sourceror] Fixed issue that prevented keyword lists from preserving their
    original format in tuples.
  • [Sourceror] get_range/1 now properly handles naked AST lists, like the ones
    coming from partial keyword lists, or stabs like a -> b.
  • [Sourceror] get_range/1 now handles partial keyword list syntax instead of
    crashing.
  • [Sourceror.Zipper] down/1 now correctly uses nil as the right siblings if
    the branch node has a single child.
  • [Sourceror] Sourceror.get_range/1 now correctly calculates the range when
    there is a comment in the same line as the node.
dorgan

dorgan

The target audience is primarily tool authors, like elixir-ls or credo.

Yes, those are the kind of use cases I had in mind :slight_smile: The Sourceror.to_string/2 function has an option to set the indentation level of the resulting code for that particular use case. I will probably add functions to know how many lines an ast node uses, so one could replace a line range instead of the whole file.

This started while exploring ways to allow credo to autofix some of the issues it finds, the multi alias expansion example derived from that.

Mostly finding what people find most cumbersome or confusing to do, I think the most important thing right now is to start experimenting. There may be some bugs in Code.quoted_to_algebra/2 too, some experiments in that front would be nice as well so we can add more regression tests to core Elixir :slight_smile:

dorgan

dorgan

Sourceror 0.7.0 is out :slight_smile:

This release adds a zipper API to improve the ergonomics of navigating and modifying the Elixir AST at will.

I added an introduction livebook to zippers: sourceror/zippers.livemd at main · doorgan/sourceror · GitHub

With this API, removing nodes or adding siblings is a straghtforward task in contrast with Macro.postwalk/Macro.prewalk. The livebook for the multi alias expansion demo was also updated with a new chapter that uses the zipper api to simplify the code.

It’s worth noting that it’s not a Sourceror specific implementation, it works for any Elixir AST. If you find yourself in the need of a zipper for your macros, Sourceror can help there too :slightly_smiling_face:

Changelog:

1. Enhancements

  • [Sourceror.Zipper] - Added a Zipper implementation for the Elixir AST based
    on Huet’s paper.
dorgan

dorgan

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.

Where Next?

Popular in Libraries Top

deadtrickster
I’ve just released stable versions of my Prometheus Elixir libs: Elixir client [docs]; Ecto collector [docs]; Plugs instrumenter/Export...
New
kevinlang
Hey all, We have made an Ecto3 Adapter for SQLite3, ecto_sqlite3! We have successfully on-boarded the full suite of integration tests (...
New
sasajuric
I’d like to announce a small library called boundaries. This is an experimental project which explores the idea of enforcing boundaries ...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. Features Unstyled Phoenix components. Storybook that can be added to...
New
zorbash
I created Kitto a framework for dashboards inspired by Dashing. [demo] The distributed characteristics of Elixir and the low memory foo...
New
Eiji
ExApi is a library that I’m developing now and hope release soon This library will allow to: list all apis list all api implementation...
New
Antrater
Hi everyone! I’m thrilled to announce a huge thing. We have been developing Elixir Moon Design System for quite a while. We are finally ...
New
engineeringdept
I’ve just released the first version of Snap, an Elasticsearch client. It borrows ideas about application structure and process managemen...
New
OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
381 12391 119
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Sub Categories:

We're in Beta

About us Mission Statement