Current section
Files
Jump to
Current section
Files
lib/mix/tasks/lazy_doc.clean.ex
defmodule Mix.Tasks.LazyDoc.Clean do
@moduledoc """
The module Mix.Tasks.LazyDoc.Clean provides functionality to clean up documentation generated for Elixir projects by removing uncommitted changes, checking the state of the project, and managing documentation entries in the Abstract Syntax Tree (AST).
## Description
This module defines a Mix task that automates the process of cleaning documentation for functions in Elixir modules. It starts by checking for any uncommitted changes in the Git repository, exiting if such changes are found to prevent data loss. If the project is clean, it proceeds to extract relevant documentation data from source files and removes specified function documentation from the AST based on information furnished by the LazyDoc application. The updated AST is then written back to the respective source files, overwriting the previous documentation entries.
Key functionality includes:
- Starting the LazyDoc application.
- Checking for a clean Git state before proceeding with documentation cleanup.
- Extracting and processing ASTs to delete specified function documentation.
- Writing the modified ASTs back to the original files to maintain up-to-date documentation consistency.
"""
require Logger
use Mix.Task
@doc File.read!("priv/lazy_doc/mix/tasks/lazy_doc.clean/run.md")
def run(args) do
Mix.Task.run("app.config")
main(args)
exit({:shutdown, 0})
end
@doc false
def main(_args) do
if not clean_tree?() do
IO.puts("Uncommitted changes detected.\nPlease stash your changes before running this task")
exit({:shutdown, 1})
end
LazyDoc.Util.extract_data_from_files()
|> Enum.each(fn entry ->
Logger.info("Cleaning #{entry.file}")
ast =
Enum.reduce(entry.functions_documented, entry.ast, fn {_mod, mod_ast, functions}, acc ->
delete_function_docs_from_ast(acc, functions, mod_ast)
end)
functions_documented? =
not Enum.all?(entry.functions_documented, fn {_mod, _mod_ast, functions} ->
Enum.empty?(functions)
end)
write_files(functions_documented?, entry, ast)
end)
end
defp write_files(must_write?, entry, ast) do
if must_write? do
elem = Enum.at(entry.functions_documented, 0)
compile_path =
elem
|> then(fn {mod, _mod_ast, _} -> mod end)
|> :code.which()
|> Path.relative_to_cwd()
|> Path.dirname()
Mix.Tasks.LazyDoc.write_to_file_formatted(entry.file, compile_path, ast, entry.comments)
end
end
defp delete_function_docs_from_ast(acc, functions, mod_ast) do
Enum.reduce(functions, acc, fn {:function, {function_atom, _function_stringified}}, acc_ast ->
delete_doc_from_ast(acc_ast, mod_ast, function_atom)
end)
end
## It will work if we suppose @doc is on top of the function.
@doc File.read!("priv/lazy_doc/mix/tasks/lazy_doc.clean/delete_doc_from_ast.md")
def delete_doc_from_ast(ast, module_ast, name_func) do
{new_ast, _acc} =
Macro.traverse(
ast,
[],
fn
{:defmodule, _meta_mod,
[
{:__aliases__, _meta_aliases, ^module_ast},
[{{:__block__, _meta_block, [:do]}, {:__block__, _meta_inner_block, block_children}}]
]} = ast,
acc ->
{ast,
[
Enum.find_index(block_children, fn node ->
match?(
{:def, _meta_def, [{^name_func, _meta_func, _params}, _func_children]},
node
) or
match?(
{:def, _meta_def,
[
{:when, _meta_when, [{^name_func, _meta_func, _params}, _when_expr]},
_func_block
]},
node
)
end)
| acc
]}
other, acc ->
{other, acc}
end,
fn
{:defmodule, meta_mod,
[
{:__aliases__, _meta_aliases, ^module_ast} = aliases_node,
[{{:__block__, meta_block, [:do]}, {:__block__, meta_inner_block, block_children}}]
]},
[index | rest] ->
new_do_block = [
{{:__block__, meta_block, [:do]},
{:__block__, meta_inner_block, List.delete_at(block_children, index - 1)}}
]
{{:defmodule, meta_mod, [aliases_node, new_do_block]}, [{index, index - 1} | rest]}
other, acc ->
{other, acc}
end
)
new_ast
end
@doc File.read!("priv/lazy_doc/mix/tasks/lazy_doc.clean/clean_tree?.md")
def clean_tree?() do
if File.exists?(".git") do
"git"
|> System.cmd(["diff-files", "--quiet"])
|> clean_tree()
else
true
end
end
defp clean_tree({_, 0}), do: true
defp clean_tree({_, 1}), do: false
end