defmodule Tidy do @moduledoc """ Documentation for Tidy. """ alias Tidy.{Config, Inspection, Report} def analyze_app(app, config \\ Config.load()) do with {:ok, modules} <- :application.get_key(app, :modules) do modules |> Enum.reject(&(&1 in config.ignore.modules)) |> Enum.map(&analyze(&1, config)) |> Enum.reject(&(&1.reference.type == :protocol)) end end def analyze(module, config \\ Config.load()) do with {:ok, data} <- Inspection.inspect_module(module, config) do report = %Report{module: module, reference: data} Enum.reduce(config.checks, report, &perform_check/2) end end def errors?(report, opts \\ []) def errors?(%Report{errors: errors}, opts) do errors.function |> Enum.flat_map(&elem(&1, 1)) |> Kernel.++(errors.module) |> filter_check(listify(opts[:check])) |> filter_type(listify(opts[:type])) |> Enum.count() end def errors?(reports, opts) when is_list(reports) do reports |> Enum.map(&errors?(&1, opts)) |> Enum.sum() end defp perform_check(check = {mod, _}, report) do perform_check(mod.scope, check, report) end defp perform_check(:module, {check, opts}, report) do case check.check(report.reference, opts) do :ok -> report {:error, short, long} -> error = %{ level: opts[:level] || :error, short: short, long: long, type: check } %{report | errors: Map.update!(report.errors, :module, &[error | &1])} end end defp perform_check(:function, {check, opts}, report) do Enum.reduce(report.reference.functions, report, fn function, report -> case check.check(function, opts) do :ok -> report {:error, short, long} -> error = %{ level: opts[:level] || :error, short: short, long: long, type: check } function_errors = Map.update( report.errors.function, {function.name, function.arity}, [error], &[error | &1] ) %{report | errors: Map.put(report.errors, :function, function_errors)} end end) end def output(report = %Report{}) do IO.puts(""" --- #{report.reference.module}: --- """) end def errors(report, opts \\ []) def errors(reports, opts) when is_list(reports), do: reports |> Enum.map(&errors(&1, opts)) |> IO.iodata_to_binary() def errors(%Report{reference: ref, errors: errors}, opts) do module = case errors.module do [] -> "" errors -> errors |> filter_check(listify(opts[:check])) |> filter_type(listify(opts[:type])) |> Enum.map(fn %{short: error} -> " #{error}" end) |> Enum.join("\n") |> Kernel.<>("\n") end function = case Enum.to_list(errors.function) do [] -> "" errors -> errors |> Enum.flat_map(fn {{fun, arity}, fun_errors} -> err = fun_errors |> filter_check(listify(opts[:check])) |> filter_type(listify(opts[:type])) |> Enum.map(fn %{short: error} -> " #{error}" end) [ "\n #{IO.ANSI.cyan()}#{fun}/#{arity}#{IO.ANSI.reset()}:" | err ] end) |> Enum.join("\n") |> Kernel.<>("\n") end result = module <> function if result != "" do [ [ IO.ANSI.yellow_background(), "\n", IO.ANSI.black(), " ", String.trim_leading("#{ref.id}", "Elixir."), IO.ANSI.reset() ], "\n", result ] |> IO.iodata_to_binary() else "" end end defp filter_check(list, []), do: list defp filter_check(list, checks), do: Enum.filter(list, &(&1.type in checks)) defp filter_type(list, []), do: list defp filter_type(list, types), do: Enum.filter(list, &(&1.type.category in types)) defp listify(nil), do: [] defp listify(list) when is_list(list), do: list defp listify(item), do: [item] end