defmodule Credo.Code.Block do
@moduledoc """
This module provides helper functions to analyse blocks, e.g. the block taken
by the `if` macro.
"""
@doc """
Returns the do: block of a given AST node.
"""
def all_blocks_for!(ast) do
[
do_block_for!(ast),
else_block_for!(ast),
rescue_block_for!(ast),
after_block_for!(ast)
]
end
@doc """
Returns true if the given `ast` has a do block.
"""
def do_block?(ast) do
case do_block_for(ast) do
{:ok, _block} ->
true
nil ->
false
end
end
@doc """
Returns the do: block of a given AST node.
"""
def do_block_for!(ast) do
case do_block_for(ast) do
{:ok, block} ->
block
nil ->
nil
end
end
@doc """
Returns a tuple {:ok, do_block} or nil for a given AST node.
"""
def do_block_for({_atom, _meta, arguments}) when is_list(arguments) do
do_block_for(arguments)
end
def do_block_for(do: block) do
{:ok, block}
end
def do_block_for(do: do_block, else: _else_block) do
{:ok, do_block}
end
def do_block_for(arguments) when is_list(arguments) do
Enum.find_value(arguments, &find_keyword(&1, :do))
end
def do_block_for(_) do
nil
end
@doc """
Returns true if the given `ast` has an else block.
"""
def else_block?(ast) do
case else_block_for(ast) do
{:ok, _block} ->
true
nil ->
false
end
end
@doc """
Returns the `else` block of a given AST node.
"""
def else_block_for!(ast) do
case else_block_for(ast) do
{:ok, block} ->
block
nil ->
nil
end
end
@doc """
Returns a tuple {:ok, else_block} or nil for a given AST node.
"""
def else_block_for({_atom, _meta, arguments}) when is_list(arguments) do
else_block_for(arguments)
end
def else_block_for(do: _do_block, else: else_block) do
{:ok, else_block}
end
def else_block_for(arguments) when is_list(arguments) do
Enum.find_value(arguments, &find_keyword(&1, :else))
end
def else_block_for(_) do
nil
end
@doc """
Returns true if the given `ast` has an rescue block.
"""
def rescue_block?(ast) do
case rescue_block_for(ast) do
{:ok, _block} ->
true
nil ->
false
end
end
@doc """
Returns the rescue: block of a given AST node.
"""
def rescue_block_for!(ast) do
case rescue_block_for(ast) do
{:ok, block} ->
block
nil ->
nil
end
end
@doc """
Returns a tuple {:ok, rescue_block} or nil for a given AST node.
"""
def rescue_block_for({_atom, _meta, arguments}) when is_list(arguments) do
rescue_block_for(arguments)
end
def rescue_block_for(do: _do_block, rescue: rescue_block) do
{:ok, rescue_block}
end
def rescue_block_for(arguments) when is_list(arguments) do
Enum.find_value(arguments, &find_keyword(&1, :rescue))
end
def rescue_block_for(_) do
nil
end
@doc """
Returns true if the given `ast` has an catch block.
"""
def catch_block?(ast) do
case catch_block_for(ast) do
{:ok, _block} ->
true
nil ->
false
end
end
@doc """
Returns the catch: block of a given AST node.
"""
def catch_block_for!(ast) do
case catch_block_for(ast) do
{:ok, block} ->
block
nil ->
nil
end
end
@doc """
Returns a tuple {:ok, catch_block} or nil for a given AST node.
"""
def catch_block_for({_atom, _meta, arguments}) when is_list(arguments) do
catch_block_for(arguments)
end
def catch_block_for(do: _do_block, catch: catch_block) do
{:ok, catch_block}
end
def catch_block_for(arguments) when is_list(arguments) do
Enum.find_value(arguments, &find_keyword(&1, :catch))
end
def catch_block_for(_) do
nil
end
@doc """
Returns true if the given `ast` has an after block.
"""
def after_block?(ast) do
case after_block_for(ast) do
{:ok, _block} ->
true
nil ->
false
end
end
@doc """
Returns the after: block of a given AST node.
"""
def after_block_for!(ast) do
case after_block_for(ast) do
{:ok, block} ->
block
nil ->
nil
end
end
@doc """
Returns a tuple {:ok, after_block} or nil for a given AST node.
"""
def after_block_for({_atom, _meta, arguments}) when is_list(arguments) do
after_block_for(arguments)
end
def after_block_for(do: _do_block, after: after_block) do
{:ok, after_block}
end
def after_block_for(arguments) when is_list(arguments) do
Enum.find_value(arguments, &find_keyword(&1, :after))
end
def after_block_for(_) do
nil
end
defp find_keyword(list, keyword) when is_list(list) do
if Keyword.has_key?(list, keyword) do
{:ok, list[keyword]}
else
nil
end
end
defp find_keyword(_, _) do
nil
end
@doc """
Returns the children of the `do` block of the given AST node.
"""
def calls_in_do_block({_op, _meta, arguments}) do
arguments
|> do_block_for!
|> instructions_for
end
def calls_in_do_block(arg) do
arg
|> do_block_for!
|> instructions_for
end
@doc """
Returns the children of the `rescue` block of the given AST node.
"""
def calls_in_rescue_block({_op, _meta, arguments}) do
arguments
|> rescue_block_for!
|> instructions_for
end
def calls_in_rescue_block(arg) do
arg
|> rescue_block_for!
|> instructions_for
end
@doc """
Returns the children of the `catch` block of the given AST node.
"""
def calls_in_catch_block({_op, _meta, arguments}) do
arguments
|> catch_block_for!
|> instructions_for
end
def calls_in_catch_block(arg) do
arg
|> catch_block_for!
|> instructions_for
end
defp instructions_for({:__block__, _meta, calls}), do: calls
defp instructions_for(v)
when is_atom(v) or is_tuple(v) or is_binary(v) or is_float(v) or is_integer(v),
do: List.wrap(v)
defp instructions_for(v) when is_list(v), do: [v]
end