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:
https://github.com/doorgan/formatter/blob/main/lib/examples/multi_alias.ex#L32-L50
Or a function to add a dependency to mix.exs ala npm install:
https://github.com/doorgan/formatter/blob/main/lib/examples/deps_add.ex#L34-L76
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.
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 14 Posts
a8t
Random, pretty particular question. I was curious about writing something that could alphabetize my Mix dependencies. Would this be the right tool to build that with?
dorgan
Yes! What you have to be mindful of with this kind of manipulations in contrast with macros is that you need to consider how line numbers move around. You need to both reorder the dependencies, and correct the line numbers, otherwise comments may be misplaced. The reason is that
Code.quoted_to_algebrarequires the ast and comments as separate arguments and mixes them by their line numbers.One way to achieve what you want is by doing this:
The other thing to note is that this is not your regular AST, Sourceror uses the
literal_encoder: &{:ok, {:__block__, &2, [&1]}}option forCode.string_to_quoted_with_comments/2under the hood, so you need to expect literals to be wrapped in blocks, so for example{:a, "~> 1.0"}will become:This is explained a bit in the Formatting considerations section of the new functions
Of course I will try to expand Sourceror as we find more complex use cases that could be simplified
AndyL
@dorgan this is exciting work - thank you!
Can you talk a little bit about who is the target audience, and your vision for possible use-cases?
Could this be used by something like
elixir-lsto implement refactoring operations? (egrename variable,rename function,extract function,inline function,rename module)What types of contributions and testing would be most useful to you?
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
The
Sourceror.to_string/2function 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/2too, some experiments in that front would be nice as well so we can add more regression tests to core Elixirdorgan
Sourceror is now available on hex.pm and supports Elixir versions down to 1.10
AndyL
@dorgan - thanks for the support down to 1.10!
Here’s a question about Sourceror and Elixir types and doctests…
I expect that a Sourceror transformation could modify a function signature or a return type…
Would Sourceror also have the ability to transform typespecs? Or inline documentation? (eg for doctests)
dorgan
It should be possible, it’s information you have in the AST
For example:
Typespecs are just module attributes, same for doc annotations, so you should be able to get the module attributes associated to a function by walking the tree with
Macro.postwalkorSourceror.postwalkif you’re also doing some transformation. You need to make some assumptions, though, for instance, considering a module attribute that comes before a function definition as being an annotation for such function. For doctests, since you already have access to the docstrings(they’re module attributes), it would be a matter of parsing the docstring looking for any doctest and then run Sourceror functions on it.I think the real difficulty comes from the fact that you need to change a node that is not a children of the function, and that comes before the function. My first thought is that by postwalking, if you go up and find a function, and want to update it’s typespec, there could be a way to tell the postwalker that an update should be performed in a sibling node, maybe passing a callback and making it reduce over the parent’s children(I do something similar in the multi-alias expansion example). All of this while also applying line corrections.
I believe it is possible because we have all the data we need, but it’s something I need to put some thought on to be able to do it in a reliable and relatively straightforward way, and it would definitely be something worth adding to the library
dorgan
Sourceror v0.4.0 was released.
It fixes a couple bugs, makes several improvements to comments line corrections, and adds more functions to work with ranges and positions, like
Sourceror.get_range/1andSourceror.compare_positions/2.I also converted the multi alias expansion example into a proper document you can import to Livebook, with step-by-step explanations of the process: sourceror/notebooks/expand_multi_alias.livemd at main · doorgan/sourceror · GitHub
You can see the full changelog here.
egze
Was thinking in the same direction, but writing a Credo check. A script to auto-sort the deps would be nice (although my editor currently does it also without issues)
dorgan
Sourceror 0.6.0 is out
This release introduces some breaking changes, as the way comments are handled by the library has been fundamentally changed. In essence, instead of requiring the user to calculate how line numbers should be shifted, Sourceror tries to “fix” the line numbers in a way that makes sense for the Elixir formatter when you call
Sourceror.to_string/2orSourceror.extract_comments/2.To illustrate this, the dependency sorting example can now be reduced to this traversal:
Note that, because the line number correction hack is no longer needed, traversals over Sourceror’s AST is the same as traversals over regular AST, just don’t discard comments metadata
The multi alias expansion livebook was further simplified thanks to this change.
Also, now
Sourceror.get_range/1returns the actual start and end positions for any node(provided it has line and column metadata), making it suitable for things like “replace the contents between these two positions with this new content”. Column offsets are counted as UTF-8 offsets(like the regular Elixir AST), so tools that want to support the Language Server Protocol need to convert them to UTF-16 offsets.Changelog:
1. Enhancements
to_stringno longer requires line number corrections to produce properly formatted code.prewalk/2andprewalk/3.parse_stringwon’t warn on unnecesary quotes.Sourceror.PostwalkStatewas renamed toSourceror.TraversalStateto make it more generic for other kinds of traversals.2. Removals
get_line_spanwas removed in favor of usingget_rangeand calculating the difference from the range start and end lines.line_correctionfield was removed as it is no longer needed.3. Bug fixes
get_rangenow properly returns ranges that map a node to it’s actual start and end positions in the original source code.