Giter Site home page Giter Site logo

mix_unused's Introduction

Mix Unused

Module Version Hex Docs Total Download License Last Updated CodeCov

Mix compiler tracer for detecting unused public functions.

Installation

def deps do
  [
    {:mix_unused, "~> 0.3.0"}
  ]
end

The docs can be found at https://hexdocs.pm/mix_unused.

Usage

After installation you need to add :unused as a compiler to the list of Mix compilers:

defmodule MySystem.MixProject do
  use Mix.Project

  def project do
    [
      compilers: [:unused] ++ Mix.compilers(),
      # In case of Phoenix projects you need to add it to the list
      # compilers: [:unused, :phoenix, :gettext] ++ Mix.compilers()
      # ...
      #
      # If you want to only run it in the dev environment you could do
      # it by using `compilers: compilers(Mix.env()) ++ Mix.compilers()`
      # instead and then returning the right compilers per environment.
    ]
  end

  # ...
end

Then you just need to run mix compile or mix compile --force as usual and unused hints will be added to the end of the output.

Warning

This isn't perfect solution and this will not find dynamic calls in form of:

apply(mod, func, args)

So this mean that, for example, if you have custom child_spec/1 definition then mix unused can return such function as unused even when you are using that indirectly in your supervisor.

Configuration

You can define used functions by adding mfa in unused: [ignored: [⋯]] in your project configuration:

def project do
  [
    # ⋯
    unused: [
      ignore: [
        {MyApp.Foo, :child_spec, 1}
      ]
    ],
    # ⋯
  ]
end

Copyright and License

Copyright © 2021 by Łukasz Niemier

This work is free. You can redistribute it and/or modify it under the terms of the MIT License. See the LICENSE file for more details.

mix_unused's People

Contributors

hauleth avatar justincy avatar kianmeng avatar marc0s avatar noozo avatar pragtob avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

mix_unused's Issues

** (MatchError) no match of right hand side value: {:error, {:already_loaded, :my_app}}

iex(2)> recompile
Compiling 1 file (.ex)
** (MatchError) no match of right hand side value: {:error, {:already_loaded, :my_app}}
    (mix_unused 0.2.1) lib/mix/tasks/compile.unused.ex:168: Mix.Tasks.Compile.Unused.all_functions/1
    (mix_unused 0.2.1) lib/mix/tasks/compile.unused.ex:132: Mix.Tasks.Compile.Unused.after_compiler/4
    (elixir 1.13.4) lib/enum.ex:2396: Enum."-reduce/3-lists^foldl/2-0-"/3
    (mix 1.13.4) lib/mix/tasks/compile.all.ex:72: Mix.Tasks.Compile.All.compile/4
    (mix 1.13.4) lib/mix/tasks/compile.all.ex:59: Mix.Tasks.Compile.All.with_logger_app/2
    (mix 1.13.4) lib/mix/tasks/compile.all.ex:36: Mix.Tasks.Compile.All.run/1
    (mix 1.13.4) lib/mix/task.ex:397: anonymous fn/3 in Mix.Task.run_task/3
    (mix 1.13.4) lib/mix/tasks/compile.ex:131: Mix.Tasks.Compile.run/1

How to pass options to compiler (namely severity)?

Looking at the code, severity is read from options, not from the project config, so putting that config alongside the ignore will also not work, right?

I trying adding a tuple in the compiler with the options inside, like this:

{:unused, [severity: "error"]}

But that doesn't seem to be supported by mix compiler. Any other way i can setup unused to fail my compilation if there are unused functions?

Thanks!

Possible to ignore based on folder (for instance /test)?

Hi,

I found several points in my project where when i have something like
def some_func(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3)

And if only use some_func(1, 2, 3) in a test function (like a fixture or something like that) i get an unused warning. I was wondering if mix_unused is working for test code as well and, if not, if there is a way to ignore an entire folder (not modules, but really a folder).

Thanks!

Compilation error: protocol Enumerable not implemented for nil of type Atom

Hi,

When trying to use the package, unfortunately, the compilation fails with the following error. It seems that Application.spec/2 somehow is returning nil. This is in a context of an umbrella app, if that matters.

could not compile dependency :my_app, "mix compile" failed.
You can recompile this dependency with "mix deps.compile my_app", update it with "mix deps.update my_app" or clean it with "mix deps.clean my_app"

** (Protocol.UndefinedError) protocol Enumerable not implemented for nil of type Atom
(elixir 1.12.3) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir 1.12.3) lib/enum.ex:141: Enumerable.reduce/3
(elixir 1.12.3) lib/enum.ex:3958: Enum.flat_map/2
(mix_unused 0.4.0) lib/mix_unused/exports.ex:22: MixUnused.Exports.application/1
(mix_unused 0.4.0) lib/mix/tasks/compile.unused.ex:136: Mix.Tasks.Compile.Unused.after_compiler/5
(elixir 1.12.3) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
(mix 1.12.3) lib/mix/tasks/compile.all.ex:72: Mix.Tasks.Compile.All.compile/4
(mix 1.12.3) lib/mix/tasks/compile.all.ex:59: Mix.Tasks.Compile.All.with_logger_app/2

Not working correctly with umbrella projects

I tried using this with an umbrella project there are issues that I found. Note that I ran mix compile at root of project

Issue one:
put compilers: [:unused] ++ Mix.compilers() in the root mix.exs file will generate 0 result (I purposely put a unused function in one of the sub project that is inside the umbrella)

Issue two
put compilers: [:unused] ++ Mix.compilers() in all sub projects' mix.exs file will generate false postivies (ie subproject one function will be deem unused even though it is used by subproject two)

Regex in documentation doesn't render correctly on hexdocs.pm

I tried to copy the example ignore configuration from https://hexdocs.pm/mix_unused/Mix.Tasks.Compile.Unused.html#module-patterns

I noticed it wasn't working as expected (it didn't ignore __phoenix_recompile__?/0). It looks like the escaped characters \? and \. are rendered without the leading \ on hexdocs.pm.

{:_, ~r/^__.+__\??$/, :_},
{~r/^MyAppWeb\..*Controller/, :_, 2},

I'm not 100% sure what's the best way to fix it or if it's even a problem with how the documentation is written in this library or with ExDoc itself. That's why I'm submitting an issue first instead of a PR 🙂.

Mark transitive calls as unused as well

This is just proposal and question toward community:

Should this report also transitive calls as unused? For example in this code (assume that this is all code):

defmodule Foo do
  @doc export: true
  def exported do
    foo()
  end

  def unused do
    foo()
    bar()
  end

  def foo, do: :ok

  def bar, do: :ok
end

Foo.unused/0 will be marked as unused currently. Question is whether bar/0 should be marked as unused as well? Currently you need to do several "edit, recompile" loops to remove all unused code to check for transitive unused functions. Such change, while handy, would slightly make the whole tracing a little bit more complex (however it shouldn't be super hard as IIRC all information is already there).

error running mix task

iex version:

$ iex
Erlang/OTP 24 [erts-12.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
Interactive Elixir (1.12.3) - press Ctrl+C to exit (type h() ENTER for help)

once i ran the command it failed

$ mix compile.unused
** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.281.0>}}
    lib/mix/tasks/compile.unused.ex:90: Mix.Tasks.Compile.Unused.run/1
    (mix 1.12.3) lib/mix/task.ex:394: anonymous fn/3 in Mix.Task.run_task/3
    (mix 1.12.3) lib/mix/project.ex:353: Mix.Project.in_project/4
    (elixir 1.12.3) lib/file.ex:1560: File.cd!/2
    (mix 1.12.3) lib/mix/task.ex:520: anonymous fn/4 in Mix.Task.recur/1
    (elixir 1.12.3) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (mix 1.12.3) lib/mix/task.ex:519: Mix.Task.recur/1
    (mix 1.12.3) lib/mix/project_stack.ex:181: Mix.ProjectStack.recur/1

using

{:mix_unused, "~> 0.2.0"}

False positives? (marks many functions that are definitely used)

👋

Hi there, and thanks for your work on this and general OSS contributions. I tried this out on a bigger properitary code base, so sadly can't share it to reproduce.

But what I found is that it reports way too many cases. I wonder why that is.

It sometimes works. It sometimes doesn't.

Background

➜  git:(mix-unused-code) ✗ elixir -v
Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Elixir 1.13.4 (compiled with Erlang/OTP 25)
➜  git:(mix-unused-code) ✗  mix deps | grep unused
* mix_unused 0.4.0 (Hex package) (mix)
  locked at 0.4.0 (mix_unused) 082f6b34

It's a Phoenix project, relatively standard with Project and ProjectWeb. The configuration for unused is:

      unused: [
        ignore: [
          {Project.Fixtures, :_, :_},
          {Project.Factory, :_, :_},
          # Make more specific later
          {~r/ProjectWeb\..*View/, :_, :_},
          # yeah it doesn't get the connections from routes to these it seems...
          {~r/ProjectWeb\..*Controller/, :_, :_},
          {ProjectWeb.Router.Helpers, :_, :_},
          {ProjectWeb.Router, :_, :_},
          {ProjectWeb.Gettext, :_, :_},
          {ProjectWeb.Endpoint, :_, :_},
          # te,[praru. see,s weord]
          {~r/ProjectWeb\.ApiDocs.*/, :_, :_},
          {~r/Project\.Scripts.*/, :_, :_},
          {Project.Repo, :_, :_},
          {:_, :__schema__, 2},
          {:_, :__changeset__, 2},
          {:_, :__enums__, 2},
          {:_, :__enum_map__, 2},
          {FixturesSupport, :_, :_}
        ]
      ]

I only add the compiler in the dev environment and then run mix compile --force.

Cases

One case of unused function that is definitely used

hint: Project.Billing.fetch_bank_account_for_wise_from_withdrawal_method/1 is unused
    lib/project/billing.ex:86

This function is called in 2 places. For instance: Project.PaymentProcessor.Service.PayContractorInvoice - it's inside a big with statement within the call function.

{:ok, bank_account} <-
           Billing.fetch_bank_account_for_wise_from_withdrawal_method(invoice.withdrawal_method_id),

Seems like it should be straight forward.

I was worried that it might be a transitive thing, but Project.PaymentProcessor.Service.PayContractorInvoice.call/2 is not mentioned as being unused. In fact, a function it uses is even highlighted as "should be private".

So... I'm confused.

Another unused case where it is definitely used

hint: Tiger.Billing.set_user_default_bank_account/2 is unused
    lib/tiger/billing.ex:195

That one is used in Project.Billing.Service.SetUserDefaultBankAccount.call/2 up in a case:

    case Billing.set_user_default_bank_account(current_user, bank_account) do

🤷

Correct unused

In the same module, there are also cases where the library is correct.

hint: Project.Billing.list_bank_accounts/0 is unused
    lib/project/billing.ex:36

Is indeed unused and can be removed.

Correct used

It doesn't report on get_bank_account_by_slug for instance. And it is correct, that function is indeed used.


Let me know what I could do to help you debug this or debug it myself :)

Thanks for all your work!

IMG_20190127_140756

Incompatible with Elixir 1.10?

== Compilation error in file lib/mix_unused.ex ==
** (CompileError) lib/mix_unused.ex:55: cannot import Mix.Compilers.Elixir.read_manifest/2 because it is undefined or private
    (elixir 1.10.2) src/elixir_import.erl:64: :elixir_import.calculate/6
    (elixir 1.10.2) src/elixir_import.erl:18: :elixir_import.import/4

@hauleth, any chance of an update? This seems to be the only dep out there to provide dead code detection.

Add option to ignore documented functions in documented modules

It can be useful for library authors, as documented functions are "naturally" exported and meant for the end user to be used. So if function is not hidden and available in not hidden module, then it can be treated as automatically exported. Hidden functions and functions in hidden modules should be treated as non-exported and there should be warning on them.

Export: true should cascade on transitive calls

Hello,
it would be nice if transitive calls starting from a root function marked as "@export: true" are not maked as unused.
E.g.

defmodule Foo do
  @doc export: true
  def exported do
    used_only_externally()
  end

  def used_only_externally do
    foo()
    bar()
  end

  def foo, do: :ok

  def bar, do: :ok
end

Right now, used_only_externally is marked as unused, but it is not.

Thanks a lot.

Handling false positives

hint: AppDB.TableOne.__changeset__/0 is unused
    lib/Schemas/TableOne.ex:5

hint: AppDB.TableOne.__schema__/1 is unused
    lib/Schemas/TableOne.ex:5

hint: AppDB.TableOne.__schema__/2 is unused
    lib/Schemas/TableOne.ex:5

hint: AppDB.TableOne.__struct__/0 is unused
    lib/Schemas/TableOne.ex:5

hint: AppDB.TableOne.__struct__/1 is unused
    lib/Schemas/TableOne.ex:5

hint: AppDB.TableOne.changeset/2 is unused
    lib/Schemas/TableOne.ex:15

hint: Server.Alpha.child_spec/1 is unused
    lib/GenServers/Server.Alpha.ex:2

hint: Server.Alpha.start_link/1 is unused
    lib/GenServers/Server.Alpha.ex:4

hint: Supervisor.Main.child_spec/1 is unused
    lib/Life Cycle Managers/Supervisor.Main.ex:2

hint: AppDB.Repo.child_spec/1 is unused
    lib/Schemas/repo.ex:2

hint: AppDB.Repo.query/3 is unused
    lib/Schemas/repo.ex:1

hint: AppDB.Repo.query!/3 is unused
    lib/Schemas/repo.ex:1

hint: AppDB.Repo.to_sql/2 is unused
    lib/Schemas/repo.ex:1

First off, great project it's been a life-saver! :) Quick question: how can I keep these false positives from triggering?

mute on defstruct and defdelegate

Hi, something I spotted when trying to run on one of my toy projects:
I'm using gen_state_machine and depending on a module variant I pass to my machine it uses different logic.
mix_unused catches all my defstruct definitions and delegates as unused:

  defdelegate fetch(term, key), to: Map
  defdelegate get(term, key, default), to: Map
  defdelegate get_and_update(term, key, fun), to: Map
  defstruct @valid_moves
hint: %Score{} is unused
    lib/variants/score.ex:110

hint: Score.fetch/2 is unused
    lib/variants/score.ex:107

hint: Score.get/3 is unused
    lib/variants/score.ex:108

hint: Score.get_and_update/3 is unused
    lib/variants/score.ex:109

Suggestion: ignore predicate

First of all, this is an awesome library with high quality code inside!

Overview

My suggestion is instead of having this code as a part of MixProject.project().unused.ignore option:

[
  {:_, :child_spec, 1},
  {:_, ~r/^__.+__\??$/, :_},
  {~r/^MyAppWeb\..*Controller/, :_, 2},
  {MyApp.Test, :foo, 1..2}
]

We can have a predicate closure which will look something like:

fn
  _, :child_spec, 1 -> false,
  _, function, _ ->
    ...
end

But matching filter is very nice and handy, so we can have a macro which will look like:

matching_filter([
  {:_, :child_spec, 1},
  {:_, ~r/^__.+__\??$/, :_},
  {~r/^MyAppWeb\..*Controller/, :_, 2},
  {MyApp.Test, :foo, 1..2}
])

And will compile list of matches into a closure

Why

  1. Closures are more flexible
  2. Everybody knows how fn works, while matching requires some additional knowledge
  3. Matching predicate compiler is too awesome to be an internal part of this project

Not working with dependency injection via module attribute?

The library seems to be doing false positives when we have dependency injection (i.e the call is not directly mapped in the code), like for instance:

  @customers_impl Application.compile_env(
                    :subscription_service,
                    :customers,
                    SubscriptionServiceApi.Customers.Impl
                  )
                  
                  @customers_impl.do_something()

In this example, seems like do_something will be reported as unused, even though it is.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.