Skip to content

Commit 3e29cae

Browse files
committed
Add experimental mix styler.remove_unused task
1 parent 77861bf commit 3e29cae

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

lib/mix/tasks/remove_unused.ex

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2025 Adobe. All rights reserved.
2+
# This file is licensed to you under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License. You may obtain a copy
4+
# of the License at http://www.apache.org/licenses/LICENSE-2.0
5+
6+
# Unless required by applicable law or agreed to in writing, software distributed under
7+
# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
# OF ANY KIND, either express or implied. See the License for the specific language
9+
# governing permissions and limitations under the License.
10+
11+
defmodule Mix.Tasks.Styler.RemoveUnused do
12+
@shortdoc "EXPERIMENTAL: uses unused import/alias/require compiler warnings to remove those lines"
13+
@moduledoc """
14+
WARNING: EXPERIMENTAL
15+
16+
Removes unused import/alias/require statements by compiling the app and parsing warnings, then deleting the specified lines.
17+
18+
Usage:
19+
20+
mix styler.remove_unused
21+
"""
22+
use Mix.Task
23+
24+
alias Mix.Shell.IO
25+
26+
@impl Mix.Task
27+
def run(_) do
28+
# Warnings come over stderr, so gotta redirect
29+
{output, _} = System.cmd("mix", ~w(compile --all-warnings), stderr_to_stdout: true)
30+
31+
if output =~ "warning: unused" do
32+
IO.info("Removing unused import/alias/require lines...\n")
33+
34+
output
35+
|> String.split("\n\n")
36+
|> Stream.map(&Regex.run(~r/warning\: unused (alias|require|import).* (.*\.exs?):(\d+)\:/s, &1))
37+
|> Stream.filter(& &1)
38+
|> Stream.map(fn [_full_message, _require_or_alias, file, line] ->
39+
file =
40+
if File.exists?(file) do
41+
file
42+
else
43+
[umbrella_corrected] = Path.wildcard("apps/*/#{file}")
44+
umbrella_corrected
45+
end
46+
47+
{file, String.to_integer(line)}
48+
end)
49+
|> Enum.group_by(fn {file, _} -> file end, fn {_, line} -> line end)
50+
|> Enum.sort_by(fn {file, _} -> file end)
51+
|> Enum.each(fn {file, lines} ->
52+
contents = file |> File.read!() |> String.split("\n")
53+
54+
IO.info("==> #{file}")
55+
56+
lines
57+
|> Enum.sort()
58+
|> Enum.each(&IO.info("#{&1}: #{Enum.at(contents, &1 - 1)}"))
59+
60+
contents =
61+
lines
62+
|> Enum.sort(:desc)
63+
|> Enum.reduce(contents, &List.delete_at(&2, &1 - 1))
64+
|> Enum.join("\n")
65+
66+
File.write!(file, contents)
67+
IO.info("")
68+
end)
69+
70+
IO.info("Running `mix format` to remove any excess newlines.")
71+
72+
Mix.Task.run("format")
73+
74+
IO.info("Done.")
75+
else
76+
IO.info("No \"unused\" warnings detected, no work to do.")
77+
end
78+
end
79+
end

0 commit comments

Comments
 (0)