Giter Site home page Giter Site logo

meandro's Introduction

Meandro Build Status Hex pm

Find dead code in Elixir applications

a spiraling oxbow lake, in the style of Salvador Dalí

What kind of dead code? Meandro currently has rules to find:

  • unused function arguments
  • unused struct fields
  • unused record fields
  • unused configuration options
  • unused callbacks
  • unused macros

Sample Output

If Meandro detects issues in your code, it will report them as follows…

lib/cache.ex:23 |> execute/1 doesn't need its #1 argument
lib/application.ex:15 |> maybe_evaluate/2 doesn't need its #1 argument
lib/application.ex:45 |> maybe_evaluate/3 doesn't need its #2 argument
Config files |> Configuration option :color (MIX_ENV=dev) is not used anywhere in the code

Installation

The package can be installed from hex by adding meandro to your list of dependencies in mix.exs:

def deps do
  [
    {:meandro, "~> 0.1.0"}
  ]
end

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

Usage

mix meandro

is the basic command. It'll run all configured rules (which is all of them by default) against all of the *.ex files in your application.

You can pass the --files argument if you'd like to check only particular files.

mix meandro --files lib/foo.ex,lib/bar.ex

Be warned, however, that it will only look at those files. Functions declared in those files but used elsewhere will be seen as "unused."

Configuration

The plugin supports the following configuration options in the meandro section of mix.exs:

  • rules ([Meandro.Rule.t()]):
    • This is the list of rules to apply to the analyzed code. Each rule is a module that should apply the Meandro.Rule behavior.
    • If this option is not defined, meandro will apply all of the default rules.
  • parsing (Meandro.Util.parsing_style()):
    • This parameter determines if meandro should parse files in a parallel (mapping through Task.async/1) or sequential (plain Enum.map/2) fashion.
    • The default value is parallel since it's faster.
  • ignore ([Path.t() | {Path.t(), Meandro.Rule.t() | [Meandro.Rule.t()]} | {Path.t(), Meandro.Rule.t() | [Meandro.Rule.t()], list()}]):
    • List of wildcard patterns representing the files and rules that meandro will ignore when analyzing. Tuple format is used to ignore either a specific rule or a set of rules in those files.

Example

  def project do
    [
      meandro: meandro_config(),
      ...
    ]
  end

  def meandro_config() do
    %{
      rules: [Meandro.Rule.UnnecessaryFunctionArguments],
      parsing: :sequential,
      ignore: ["test/example.ex"]
    }
  end

meandro's People

Contributors

elbrujohalcon avatar maco avatar pablocostass avatar pbrudnick avatar pehuen-rodriguez avatar tak30 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

Watchers

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

Forkers

adolfont

meandro's Issues

Improve result display

Don't just put the whole map on display, print the actual warnings alone.
And halt if there are any.

Get the AST of all pertinent files

The objective of this issue is to implement a way of, for a given list of files, obtain their ASTs and return them, for the rules to analyze.

Unused Callbacks

Rule

A rule to detect unused callbacks.
This rule will check all callbacks defined in a module and find those not used anywhere in the module itself.

It will emit a warning if it can't find the callback's atom name used anywhere within a module. It will NOT emit a warning if an atom named as a callback is being used, no matter for what that atom is used. This limitation is because Erlang has many ways to call a function (particularly when dynamic calls are involved).

The assumption is that if you define a callback for a behavior, your generic module (where the callback is defined) should call that function at some point, using the implementation provided by the specific module (the one that implements the behavior).

Example

defmodule BadBeh do
  @callback used() :: term()
  @callback unused() :: term()
  def use(mod) do
    mod.used()
  end
end

Meandro should emit a warning about the second callback.

Code cleanup: Add comments to explain what different patterns match against

A lot of the functions for analyzing code that are defined repeatedly with different headers, it can be hard to tell what semantically each one is for. They're mainly private functions, so they don't need @doc markers, but still, a comment on each saying what type of syntax gets matched there would be handy when we try to debug things in the future.

Add support for ignoring warnings

Hank has support for this at rebar.config and individual module level (through attributes).

We should find the idiomatic way to implement this in Elixir and then use the is_ignored? function on all of our rules to match ignores and patterns.

Read meandro's configuration from mix.exs

Implement a module in lib/meandro to parse the configuration that can be found in mix.exs, and call it from the mix task.

Note: the mix.exs configuration can be obtained as follows:

mix_config = Mix.Project.config()

Warns on modules with `use`

Bug Description

Phoenix defines routes which are dispatched to controller:functions.
In these modules, it sets use MyAppWeb, :controller
The functions expect 2 arguments:

def get(conn, params) do

This rule warns when ignoring the second:

def counters(conn, _params) do

To Reproduce

Ignore the second argument in a controller function in a Phoenix app.

Expected Behavior

Or ignore those or clarify if those should be manually ignored

Reenable the TODO checker in credo

I turned it off for now because it made CI fail in 1.13 before getting to the more important bits. This is a reminder to turn it back on once we have more code written.

[UnnecessaryFunctionArguments] Warns on callback implementations with `use`

Bug Description

Functions of modules using use AGivenModule which inside define callbacks and has the macro __using__ implementing those callbacks, are being analyzed as common functions instead of being ignored.

It's very related with #71 , maybe the same fix fixes both, but those are different ways of using behaviours that should be addressed.

To Reproduce

Modules implementing a module with use Module which callbacks implementations overrides the using one and ignore any argument.

Expected Behavior

Ignore the function.

Unused Macros

Rule

A rule to detect unused macros.

Example

defmodule Macri do
  defmacro unused do
    quote do
      :unused
    end
  end
end

Meandro should emit a warning, unless Macri.unused is actually used in another module.

Unnecessary Function Arguments

Rule

A rule to detect unnecessary function arguments.
The rule emits a warning for each function argument that is consistently ignored in all function clauses.

Example

defmodule Hanky do
  def a_func(_ignored, not_ignored, _not_ignored) do
    not_ignored
  end
  def a_func(_ignored, _not_ignored, not_ignored) do
    not_ignored
  end
end

Meandro should warn us about the first parameter of a_func/2.

Results enhancements

Current output is much more complex to follow than Hank's:

  • All in red (as a Mix.error())
  • Module info as a prefix is not very useful if already have file:line
  • Some names longer than Hank's
  • Unused config options rule output leak of file and line, since it uses all the env configs

I'd expect an output more like this:
https://github.com/AdRoll/rebar3_hank#sample-output

Unused Struct Field

Rule

A rule to detect unused struct fields.
The rule will detect fields defined as part of a struct but never used anywhere.

Note

This rule assumes that your code will never use the underlying map structure of your structs directly.

Example

defmodule DiscoStruct do
  defstruct used: :true, unused: :ok
  def use(param) do
    %DiscoStruct{used: u} = param
    u
  end
end

Meandro should emit a warning about the unused field, unless it's used somewhere else in the project.

Unused Record Fields

Rule

A rule to detect unused record fields.
The rule will detect fields defined as part of a record but never used anywhere.

Note

This rule assumes that your code will never use the underlying tuple structure of your records directly.

Example

defmodule Rec do
  require Record
    Record.defrecord(:my_rec, used: :true, unused: :ok)
    def use(param) do
      my_rec(param, :used)
    end
end

Meandro should emit a warning about the :unused field

[UnusedConfigurationOptions] config/3 not being properly checked

Bug Description

The rule only use a logic for analyzing config/2.

To Reproduce

Setting a config/3 in the config.exs, like a common Phoenix app does:

config :myapp, MyAppWeb.Endpoint,
  environment: Mix.env(),

Errors out:

:0 - In module : Configuration option MyAppWeb.Endpoint (MIX_ENV=dev) is not used anywhere in the code

Expected Behavior

Check the option for the context (2nd argument)

unused_macro is very slow

Bug Description

I ran the unused_macro rule on exmld, and I hit ctrl+c after an hour and a half. I don't know how much longer it would've run for.

To Reproduce

Add meandro to the config in adroll/exmld and mix meandro

Expected Behavior

Complete in a few seconds. (For reference, all other rules finish in about 1.5 seconds.)

Additional Context

exmld on  main +11 -2 [!?] via 💧 v1.12.3 (OTP 24)
❯ time mix meandro
==> meandro
Compiling 1 file (.ex)
Generated meandro app
==> exmld
Looking for oxbow lakes to dry up...
Meandro rules: [Meandro.Rule.UnusedMacros]
Meandro will use 53 files for analysis: ["config/config.exs", "examples/elixir_processor/config/config.exs", "examples/elixir_processor/deps/exmld/config/config.exs", "examples/elixir_processor/deps/exmld/examples/elixir_processor/config/config.exs", "examples/elixir_processor/deps/exmld/examples/elixir_processor/lib/elixir_processor.ex", "examples/elixir_processor/deps/exmld/examples/elixir_processor/lib/elixir_processor/application.ex", "examples/elixir_processor/deps/exmld/examples/elixir_processor/mix.exs", "examples/elixir_processor/deps/exmld/lib/exmld.ex", "examples/elixir_processor/deps/exmld/lib/exmld/kinesis_stage.ex", "examples/elixir_processor/deps/exmld/lib/exmld/kinesis_worker.ex", "examples/elixir_processor/deps/exmld/mix.exs", "examples/elixir_processor/deps/flow/lib/flow.ex", "examples/elixir_processor/deps/flow/lib/flow/coordinator.ex", "examples/elixir_processor/deps/flow/lib/flow/map_reducer.ex", "examples/elixir_processor/deps/flow/lib/flow/materialize.ex", "examples/elixir_processor/deps/flow/lib/flow/window.ex", "examples/elixir_processor/deps/flow/lib/flow/window/count.ex", "examples/elixir_processor/deps/flow/lib/flow/window/fixed.ex", "examples/elixir_processor/deps/flow/lib/flow/window/global.ex", "examples/elixir_processor/deps/flow/lib/flow/window/periodic.ex", "examples/elixir_processor/deps/flow/mix.exs", "examples/elixir_processor/deps/gen_stage/lib/consumer_supervisor.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/buffer.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/dispatcher.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/dispatchers/broadcast_dispatcher.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/dispatchers/demand_dispatcher.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/dispatchers/partition_dispatcher.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/stream.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/streamer.ex", "examples/elixir_processor/deps/gen_stage/lib/gen_stage/utils.ex", "examples/elixir_processor/deps/gen_stage/mix.exs", "examples/elixir_processor/lib/elixir_processor.ex", "examples/elixir_processor/lib/elixir_processor/application.ex", "examples/elixir_processor/mix.exs", "examples/erlang_processor/_build/default/lib/exmld/config/config.exs", "examples/erlang_processor/_build/default/lib/exmld/examples/elixir_processor/config/config.exs", "examples/erlang_processor/_build/default/lib/exmld/examples/elixir_processor/lib/elixir_processor.ex", "examples/erlang_processor/_build/default/lib/exmld/examples/elixir_processor/lib/elixir_processor/application.ex", "examples/erlang_processor/_build/default/lib/exmld/examples/elixir_processor/mix.exs", "examples/erlang_processor/_build/default/lib/exmld/lib/exmld.ex", "examples/erlang_processor/_build/default/lib/exmld/lib/exmld/kinesis_stage.ex", "examples/erlang_processor/_build/default/lib/exmld/lib/exmld/kinesis_worker.ex", "examples/erlang_processor/_build/default/lib/exmld/mix.exs", "examples/erlang_processor/_build/default/plugins/rebar3_elixir_compile/examples/demo/elixir_libs/macro_app/config/config.exs", "examples/erlang_processor/_build/default/plugins/rebar3_elixir_compile/examples/demo/elixir_libs/macro_app/lib/macro_app.ex", "examples/erlang_processor/_build/default/plugins/rebar3_elixir_compile/examples/demo/elixir_libs/macro_app/mix.exs", "examples/erlang_processor/_build/default/plugins/rebar3_elixir_compile/examples/demo/elixir_libs/macro_app/test/macro_app_test.exs", "examples/erlang_processor/_build/default/plugins/rebar3_elixir_compile/examples/demo/elixir_libs/macro_app/test/test_helper.exs", "lib/exmld.ex", ...]
^C
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
(l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
^C


Executed in 92.90 mins fish external
usr time 160.40 mins 108.00 micros 160.40 mins
sys time 4.34 mins 889.00 micros 4.34 mins

Spec things (our code) up

This ticket is, hopefully, a no-op. We should have been spec'ing things properly till now, but in the case we haven't, let's take some time to improve the quality of the code by adding specs.

Add a basic CI to the project

We don't need much to start with on the CI. Starting with a CI that checks Dialyzer, and whether the code is formatted or not should be enough. We can expand onto using credo and other stuff now or later on.

[UnnecessaryFunctionArguments] Warning on callback implementations

Bug Description

The UnnecessaryFunctionArguments rule is analyzing callback implementations which don't have set @impl before.

To Reproduce

I have this module implementing Plug:

defmodule CoophubWeb.Plug.SubdomainMatcher do
  @behaviour Plug
  import Plug.Conn, only: [put_private: 3, halt: 1]
  import Phoenix.Controller, only: [redirect: 2]

  @spec init(Keyword.t()) :: Keyword.t()
  def init(_opts) do
    Application.get_env(:coophub, CoophubWeb.Endpoint)[:url][:host]
  end

REDACTED
end

And I get:

lib/coophub_web/plug/subdomain_matcher.ex:7 - In module CoophubWeb.Plug.SubdomainMatcher: Argument in position 1 of CoophubWeb.Plug.SubdomainMatcher.init/1 is ignored in all of its clauses

Expected Behavior

Ignore callback implementations without @impl (maybe functions with same name/arity used in the module) or suggest to add it. Besides, e.g, the Plug docs don't suggest that thing https://hexdocs.pm/plug/readme.html#hello-world

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.