View Source Recode.AST (Recode v0.7.3)

This module provides functions to get informations from the AST and to manipulate the AST.

Most of the functions in this module require an AST with additional informations. This information is provided by Sourceror or Code.string_to_quoted/2 with the options

[
  columns: true,
  literal_encoder: &{:ok, {:__block__, &2, [&1]}},
  token_metadata: true,
  unescape: false
]

See also Formatting considerations in the docs for Code.quoted_to_algebra/2.

This module provides literal? functions that are also work with encoded literals generate by literal_encoder: %{:ok, {:__block__, &2, [&1]}}. For an example see atom?/1.

Summary

Types

An expression in the AST.

The metadata of an expression in the AST.

t()

Abstract Syntax Tree (AST)

Functions

Returns the infos from an AST representing an alias expression.

Concatinates the aliases of an :__aliases__ tuple.

Returns true if the given AST represents an atom.

Returns the :do or :else block arguments of the given expr.

Returns the first line in which the given AST starts.

Returns the newlines value from meta[:end_of_expression], or nil.

Returns the value from a :__block__ with a single argument.

Returns the last line in which the given AST ends.

Returns a mfa-tuple for the given .-call.

Returns the module name as an atom for the given ast.

Returns true when the given ast expression represents an expression that spans over multiple lines.

Converts AST representing a name to a string.

Puts the given value newlines under the key nevlines in meta[:end_of_expression].

Puts the given value in the :__block__ AST.

Performs a depth-first, pre-order traversal of quoted expressions and invokes fun for each node in the ast with the accumulator acc.

Reduces the ast until fun returns {:halt, term}.

Forces a one line AST expression from a multiline AST expression.

Update a function call.

Updates the function name of a capture.

Updates the AST representing a definition.

Update a dotted function call.

Updates a spec.

Types

@type acc() :: any()
@type expr() :: {atom() | expr(), metadata(), atom() | [t()]}

An expression in the AST.

@type metadata() :: keyword()

The metadata of an expression in the AST.

@type t() :: atom() | number() | binary() | [t()] | {t(), t()} | expr()

Abstract Syntax Tree (AST)

Functions

@spec alias_info(t()) :: {module(), [module()], module() | nil}

Returns the infos from an AST representing an alias expression.

The function returns 3-tuple containing the alias, the multi part and the :as.

Examples

iex> ast = quote do
...>   alias Foo.Bar
...> end
iex> alias_info(ast)
{Foo.Bar, [], nil}

iex> ast = quote do
...>   alias Foo.{Bar, Baz}
...> end
iex> alias_info(ast)
{Foo, [Bar, Baz], nil}

iex> ast = quote do
...>   alias Foo, as: Baz
...> end
iex> alias_info(ast)
{Foo, [], Baz}

iex> ast = Sourceror.parse_string!("alias __MODULE__")
iex> alias_info(ast)
{:__MODULE__, [], nil}

iex> ast = Sourceror.parse_string!("alias __MODULE__.{Foo, Bar.Baz}")
iex> alias_info(ast)
{:__MODULE__, [Foo, Bar.Baz], nil}

iex> ast = Sourceror.parse_string!("alias __MODULE__, as: MyModule")
iex> alias_info(ast)
{:__MODULE__, [], MyModule}
@spec aliases_concat({:__aliases__, metadata(), [atom()]} | list()) :: module()

Concatinates the aliases of an :__aliases__ tuple.

Examples

iex> aliases_concat({:__aliases__, [], [:Alpha, :Bravo]})
Alpha.Bravo
@spec atom?(t()) :: boolean()

Returns true if the given AST represents an atom.

Examples

iex> ":atom" |> Code.string_to_quoted!() |> atom?()
true

iex> ast = Sourceror.parse_string!(":atom")
{:__block__, [trailing_comments: [], leading_comments: [], line: 1, column: 1], [:atom]}
...> atom?(ast)
true

iex> "42" |> Sourceror.parse_string!() |> atom?()
false

iex> ast = Code.string_to_quoted!(
...>   ":a", literal_encoder: &{:ok, {:__block__, &2, [&1]}})
{:__block__, [line: 1], [:a]}
iex> atom?(ast)
true
@spec block(expr(), :do | :else) :: t()

Returns the :do or :else block arguments of the given expr.

Examples

iex> ast = Sourceror.parse_string!("""
...> defmodule Foo do
...>   def bar, do: bar
...> end
...> """)
...> block = block(ast)
...> Sourceror.to_string({:__block__,[], block})
"def bar, do: bar"

iex> {:defmodule, _meta, [_aliases, args]} = Sourceror.parse_string!("""
...> defmodule Foo do
...>   def bar, do: bar
...>   def baz, do: baz
...> end
...> """)
...> block = block(args) 
...> Sourceror.to_string({:__block__,[], block})
"""
def bar, do: bar
def baz, do: baz\
"""

iex> ast = Sourceror.parse_string!("""
...> if x, do: true, else: false
...> """)
...> block(ast, :else)
[false]
...> block(ast)
[true]
...> block(ast, :do)
[true]

iex> ast = Sourceror.parse_string!("x == y")
...> block(ast)
nil
@spec first_line(t()) :: integer() | nil

Returns the first line in which the given AST starts.

Note

The AST must be constructed by Sourceror or with the Code module and the options columns: true, token_metadata: true, literal_encoder: &{:ok, {:__block__, &2, [&1]}}.

Examples

iex> code = """
...> 1 +
...>   2 -
...>   3
...> """
...>
...> ast = Sourceror.parse_string(code)
...> first_line(ast)
1
iex> last_line(ast)
3
iex> code
...> |> Code.string_to_quoted!(
...>   columns: true,
...>   token_metadata: true,
...>   literal_encoder: &{:ok, {:__block__, &2, [&1]}})
...> |> last_line()
3
@spec get_newlines(t()) :: integer()

Returns the newlines value from meta[:end_of_expression], or nil.

@spec get_value(t()) :: term()

Returns the value from a :__block__ with a single argument.

Examples

iex> "[1, 2]"
...> |> Sourceror.parse_string!()
...> |> get_value()
...> |> Enum.map(&get_value/1)
[1, 2]
@spec last_line(t()) :: integer() | nil

Returns the last line in which the given AST ends.

See first_line/1 for example and note.

@spec mfa({{:., keyword(), list()}, metadata(), t()}) ::
  {module(), atom(), non_neg_integer()}

Returns a mfa-tuple for the given .-call.

Examples

iex> ast = quote do
...>   Foo.Bar.baz(x)
...> end
...> mfa(ast)
{Foo.Bar, :baz, 1}
@spec module(t()) :: module()

Returns the module name as an atom for the given ast.

The function accepts {:defmodule, meta, args}, the args form the :defmodule tuple or the same input as aliases_concat/1.

@spec multiline?(t() | metadata()) :: boolean()

Returns true when the given ast expression represents an expression that spans over multiple lines.

multiline? does not pay attention to do blocks.

Examples

iex> """
...> def foo(x)
...>     when is_integer(x) do
...>   {:foo, x}
...> end
...> """
...> |> Sourceror.parse_string!()
...> |> multiline?()
true

iex> """
...> def foo(x) when is_integer(x) do
...>   {:foo, x}
...> end
...> """
...> |> Sourceror.parse_string!()
...> |> multiline?()
false

iex> """
...> {
...>   x,
...>   y
...> }
...> """
...> |> Sourceror.parse_string!()
...> |> multiline?()
true

iex> """
...> {x, y}
...> """
...> |> Sourceror.parse_string!()
...> |> multiline?()
false
@spec name(atom() | [atom()]) :: String.t()

Converts AST representing a name to a string.

This function suppresses the prefix "Elixir.".

Examples

iex> name([Recode, Task])
"Recode.Task"

iex> name(Recode.Task)
"Recode.Task"
Link to this function

put_newlines(arg, newlines)

View Source
@spec put_newlines({term(), metadata(), t()}, integer()) ::
  {term(), keyword(), list()}

Puts the given value newlines under the key nevlines in meta[:end_of_expression].

@spec put_value(t(), term()) :: t()

Puts the given value in the :__block__ AST.

Examples

iex> "[1, 2]"
...> |> Sourceror.parse_string!()
...> |> get_value()
...> |> Enum.map(fn ast -> put_value(ast, "0") end)
...> |> Enum.map(&get_value/1)
["0", "0"]
@spec reduce(t(), acc(), (t(), acc() -> acc())) :: acc()

Performs a depth-first, pre-order traversal of quoted expressions and invokes fun for each node in the ast with the accumulator acc.

The initial value of the accumulator is acc. The function is invoked for each node in the ast with the accumulator. The result returned by the function is used as the accumulator for the next iteration. The function returns the last accumulator.

Examples

iex> ast = quote do
...>   def foo(x), {:oof, x}
...>   def bar(x), {:rab, x}
...> end
...> AST.reduce(ast, [], fn
...>   {:def, _, [{name, _, _}|_]}, acc -> [name | acc]
...>   ast, acc when is_atom(ast) -> [ast|acc]
...>   _ast, acc -> acc
...> end)
[:rab, :bar, :oof, :foo]
Link to this function

reduce_while(ast, acc, fun)

View Source
@spec reduce_while(t(), acc(), (t(), acc() -> result)) :: acc()
when result: {:cont, acc()} | {:halt, acc()} | {:skip, acc()}

Reduces the ast until fun returns {:halt, term}.

The return value for fun is expected to be

  • {:cont, acc} to continue the reduction with the next node in the ast and acc as the new accumulator

  • {:skip, acc} to continue the reduction while skippin the childrens of current node and acc as the new accumulator

  • {:halt, acc} to halt the reduction or

If fun returns {:halt, acc} the reduction is halted and the function returns acc. Otherwise, if the AST is exhausted, the function returns the accumulator of the last {:cont, acc}.

Examples

iex> ast = quote do
...>   def foo(x), {:oof, x}
...>   def bar(x, y), {:rab, x, y}
...> end
...>
...> AST.reduce_while(ast, [], fn
...>   {:def, _, [{name, _, args}|_]}, acc
...>     -> acc = [name | acc]
...>        if length(args) == 1 do
...>          {:cont, acc}
...>        else
...>          {:skip, acc}
...>        end
...>   ast, acc when is_atom(ast)
...>     -> {:cont, [ast|acc]}
...>   _ast, acc
...>     -> {:cont, acc}
...> end)
[:bar, :oof, :foo]
@spec to_same_line(t()) :: t()

Forces a one line AST expression from a multiline AST expression.

Example

iex> """
...> x &&
...>   y
...> """
...> |> Sourceror.parse_string!()
...> |> to_same_line()
...> |> Sourceror.to_string()
"x && y"

iex> """
...> def foo,
...>   do:
...>     :foo
...> """
...> |> Sourceror.parse_string!()
...> |> to_same_line()
...> |> Sourceror.to_string()
"""
def foo,
  do: :foo\
"""
Link to this function

update_call(arg, updates)

View Source
@spec update_call(t(), updates :: keyword()) :: t()

Update a function call.

The keyword list updates can have the keys name, meta and args.

Examples

iex> quote do
...>   foo(x)
...> end
...> |> update_call(name: :bar)
...> |> Macro.to_string()
"bar(x)"
Link to this function

update_capture(arg, list)

View Source
@spec update_capture(t(), [{:name, atom()}]) :: t()

Updates the function name of a capture.

Link to this function

update_definition(arg, updates)

View Source
@spec update_definition(t(), updates :: keyword()) :: t()

Updates the AST representing a definition.

The keyword list updates can have the keys name, meta and args.

Examples

iex> ast = Sourceror.parse_string!("def foo(x), do: x")
iex> ast |> update_definition(name: :bar) |> Macro.to_string()
"def bar(x), do: x"
iex> ast |> update_definition(args: [{:y, [], nil}]) |> Macro.to_string()
"def foo(y), do: x"
Link to this function

update_dot_call(arg, updates)

View Source
@spec update_dot_call(t(), updates :: keyword()) :: t()

Update a dotted function call.

Examples

iex> ast = quote do
...>   Foo.foo(x)
...> end
iex> update_dot_call(ast, name: :bar)
{{:., [], [{:__aliases__, [alias: false], [:Foo]}, :bar]}, [], [{:x, [], Recode.ASTTest}]}
Link to this function

update_spec(arg, updates)

View Source
@spec update_spec(t(), updates :: keyword()) :: t()

Updates a spec.

The keyword list updates can have the keys name, meta, args and return.

Examples

iex> quote do
...>   @spec foo(integer()) :: integer()
...> end
...> |> update_spec(name: :bar, return: {:term, [], []})
...> |> Macro.to_string()
"@spec bar(integer()) :: term()"