defmodule Mix.Tasks.Raxol.CheckConsistency do @moduledoc """ Checks code consistency across the Raxol codebase. ## Usage mix raxol.check_consistency [options] ## Options * `--dir` - Directory to check (default: lib/raxol) * `--format` - Output format: text, json, html (default: text) * `--severity` - Minimum severity to report: info, warning, error (default: info) * `--fix` - Attempt to auto-fix issues ## Examples mix raxol.check_consistency mix raxol.check_consistency --dir lib/raxol/terminal mix raxol.check_consistency --severity warning mix raxol.check_consistency --format json """ use Mix.Task alias Raxol.Core.Standards.ConsistencyChecker @shortdoc "Checks code consistency" @impl Mix.Task def run(args) do opts = parse_options(args) Mix.shell().info("Checking code consistency in #{opts.dir}...") case ConsistencyChecker.check_directory(opts.dir) do {:ok, report} -> filtered_report = filter_by_severity(report, opts.severity) apply_auto_fixes(opts.auto_fix, filtered_report) output_report(filtered_report, opts.format) exit_code = get_exit_code(Enum.empty?(filtered_report.issues)) System.at_exit(fn _ -> exit({:shutdown, exit_code}) end) {:error, reason} -> Mix.shell().error("Error checking consistency: #{inspect(reason)}") System.at_exit(fn _ -> exit({:shutdown, 1}) end) end end defp parse_options(args) do {opts, _, _} = OptionParser.parse(args, switches: [ dir: :string, format: :string, severity: :string, fix: :boolean ] ) %{ dir: Keyword.get(opts, :dir, "lib/raxol"), format: Keyword.get(opts, :format, "text"), severity: Keyword.get(opts, :severity, "info"), auto_fix: Keyword.get(opts, :fix, false) } end defp filter_by_severity(report, severity) do severity_level = severity_to_level(severity) filtered_issues = Enum.filter(report.issues, fn issue -> issue_level = severity_to_level(to_string(issue.severity)) issue_level >= severity_level end) %{report | issues: filtered_issues} end defp severity_to_level("error"), do: 3 defp severity_to_level("warning"), do: 2 defp severity_to_level("info"), do: 1 defp severity_to_level(_), do: 1 defp output_report(report, "json") do json = Jason.encode!(report, pretty: true) IO.puts(json) end defp output_report(report, "html") do html = generate_html_report(report) File.write!("consistency_report.html", html) Mix.shell().info("HTML report written to consistency_report.html") end defp output_report(report, _) do output = ConsistencyChecker.generate_report(report) IO.puts(output) output_issue_summary(length(report.issues)) end defp apply_fixes(report) do Mix.shell().info("Attempting to auto-fix issues...") fixable_issues = Enum.filter(report.issues, &fixable?/1) Enum.each(fixable_issues, fn issue -> case fix_issue(issue) do :ok -> Mix.shell().info( "Fixed: #{issue.file}:#{issue.line} - #{issue.message}" ) {:error, reason} -> Mix.shell().error( "Failed to fix #{issue.file}:#{issue.line} - #{reason}" ) end end) Mix.shell().info( "Fixed #{length(fixable_issues)} of #{length(report.issues)} issues" ) end defp fixable?(%{type: :formatting}), do: true defp fixable?(%{type: :trailing_whitespace}), do: true defp fixable?(_), do: false defp fix_issue(%{type: :formatting, file: file}) do case Raxol.Core.ErrorHandling.safe_call(fn -> System.cmd("mix", ["format", file]) :ok end) do {:ok, :ok} -> :ok {:error, _} -> {:error, "Failed to run mix format"} end end defp fix_issue(%{type: :trailing_whitespace, file: file, line: line}) do with {:ok, content} <- File.read(file), lines <- String.split(content, "\n"), fixed_line <- String.trim_trailing(Enum.at(lines, line - 1)), fixed_lines <- List.replace_at(lines, line - 1, fixed_line), fixed_content <- Enum.join(fixed_lines, "\n") do File.write(file, fixed_content) end end defp generate_html_report(report) do """ Raxol Code Consistency Report

Raxol Code Consistency Report

Summary

Total files analyzed: #{report.total_files}

Total issues found: #{length(report.issues)}

#{format_summary_html(report.summary)}

Issues

#{format_issues_html(report.issues)} """ end defp format_summary_html(summary) do Enum.map_join(summary, "\n", fn {type, count} -> "

#{String.capitalize(to_string(type))}: #{count}

" end) end defp format_issues_html(issues) do Enum.map_join(issues, "\n", fn {file, file_issues} -> """

#{file}

#{format_file_issues_html(file_issues)}
""" end) end defp format_file_issues_html(issues) do Enum.map_join(issues, "\n", fn issue -> """
Line #{issue.line} [#{issue.severity}]
#{issue.message}
""" end) end defp apply_auto_fixes(false, _report), do: :ok defp apply_auto_fixes(true, report) do apply_fixes(report) end defp get_exit_code(true), do: 0 defp get_exit_code(false), do: 1 defp output_issue_summary(0) do Mix.shell().info("\n✓ No consistency issues found!") end defp output_issue_summary(count) do Mix.shell().info("\nFound #{count} consistency issues") end end