Python-Credo: A static code analysis tool for the Elixir language with a focus on code consistency and teaching.

Credo CI Tests Inline docs

Credo is a static code analysis tool for the Elixir language with a focus on teaching and code consistency.

It can show you refactoring opportunities in your code, complex code fragments, warn you about common mistakes, show inconsistencies in your naming scheme and - if needed - help you enforce a desired coding style.

Credo

Installation and Usage

The easiest way to add Credo to your project is by using Mix.

Add :credo as a dependency to your project's mix.exs:

defp deps do
  [
    {:credo, "~> 1.4", only: [:dev, :test], runtime: false}
  ]
end

And run:

$ mix deps.get

$ mix credo

Documentation

Documentation is available on Hexdocs

Integrations

IDE/Editor

Some IDEs and editors are able to run Credo in the background and mark issues inline.

Automated Code Review

  • Codacy - checks your code from style to security, duplication, complexity, and also integrates with coverage.
  • SourceLevel - tracks how your code changes over time and have this information accessible to your whole team.
  • Stickler CI - checks your code for style and best practices across your entire stack.

Contributing

  1. Fork it!
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Author

René Föhring (@rrrene)

License

Credo is released under the MIT License. See the LICENSE file for further details.

Comments

  • add new check for pipe per line
    add new check for pipe per line

    Dec 23, 2021

    Some style guide proposes:

    # not preferred
    
    1 |> Integer.to_string() |> String.to_integer()
    
    # preferred
    
    1
    |> Integer.to_string()
    |> String.to_integer()
    

    This PR provides a check for that.

    --

    Credits by @bamorim

    Reply
  • What makes this function have such a high ABC?
    What makes this function have such a high ABC?

    Dec 30, 2021

    Environment

    • Credo version (mix credo -v): 1.6.1-ref.bf/credo-update.7ca054cf
    • Erlang/Elixir version (elixir -v): Elixir 1.11.2
    • Operating system: Mac OS Catalina

    What were you trying to do?

    mix credo some_file

    Expected outcome

    Expected it to pass

    Actual outcome

    Function is too complex (ABC size is 63, max is 50)

    Trying to understand why this function would be considered so complex. The default max_size is 30, so at 63 this is considered twice as complex as the max.

      def build_ship_param_item_data(invoice) do
        ShippingParameterItem
        |> distinct(true)
        |> join(:inner, [spi], assoc(spi, :shipping_parameter))
        |> join(:inner, [spi, sp], assoc(sp, :packing_slips))
        |> join(:inner, [spi, sp, ps], assoc(ps, :packing_slip_items))
        |> join(:inner, [spi, sp, ps, psi], assoc(psi, :item))
        |> join(:inner, [spi, sp, ps, psi, i], assoc(i, :invoice_items))
        |> where([spi, sp, ps, psi, i, ii], ii.invoice_id == ^invoice.id and ii.item_id == spi.item_id)
        |> group_by([spi], spi.id)
        |> select([spi, sp, ps, psi, i, ii], %{
          id: spi.id,
          amount: sum(ii.total_price * (spi.qty_assigned / ii.qty)) / 100,
          quantity: spi.qty_assigned
        })
        |> Repo.all()
      end
    

    In the world of ecto and querying, I'd consider this pretty average complexity, but ABC check disagrees. Not clear from the documentation what goes into it. https://hexdocs.pm/credo/Credo.Check.Refactor.ABCSize.html#content

    I think breaking this out into multiple functions would make it less readable. Just wondering if anyone has thoughts. I'd like to use ABC check as it is more often useful than not but I'm finding the number of places I need to add

    # credo:disable-for-next-line Credo.Check.Refactor.ABCSize

    to be very high.

    Reply
  • Unused variables should be named consistently, but `__aliases__` does not follow  that convention.
    Unused variables should be named consistently, but `__aliases__` does not follow that convention.

    Dec 31, 2021

    Precheck

    • For bugs, please do a quick search and make sure the bug has not yet been reported here :heavy_check_mark:

    Environment

    • Credo version (mix credo -v): 1.6.1
    • Erlang/Elixir version (elixir -v): Elixir 1.13.1 (compiled with Erlang/OTP 22)
    • Operating system: ubunty 18.04

    What were you trying to do?

    I have a function that matches on a Module and returns a string:

      defp module_to_string(Action), do: "action"
      defp module_to_string(Model), do: "model"
    

    edit: maybe for more insights/info these modules are aliased at the top of the file:

      alias MyApp.Actions.Action
      alias MyApp.Models.Model
    

    Expected outcome

    no report

    Actual outcome

    Unused variables should be named consistently. It seems your strategy is to name them anonymously (ie. `_`) but `__aliases__` does not follow  that convention.
    
    Reply
  • Create CondFinalCondition readability check
    Create CondFinalCondition readability check

    Jan 5, 2022

    This PR adds a check named Credo.Check.Readability.CondCatchallTrue that checks for conds that end in a catchall clause that use a literal value other than true. I created this check to catch violations of the rule about catchall cond clauses specified in the Christopher Adam's style guide (https://github.com/christopheradams/elixir_style_guide#true-as-last-condition). See the tests for examples.

    Context:

    • https://github.com/christopheradams/elixir_style_guide/issues/143#issuecomment-394191496
    Reply
  • Misleading error message when attempting to generate a custom check with the name of an existing file
    Misleading error message when attempting to generate a custom check with the name of an existing file

    Jan 9, 2022

    Precheck

    • Proposals for new features should be submitted via: https://github.com/rrrene/credo-proposals
    • For bugs, please do a quick search and make sure the bug has not yet been reported here

    Environment

    • Credo version (mix credo -v): 1.6.1
    • Erlang/Elixir version (elixir -v): 1.13.0
    • Operating system: Debian GNU/Linux 10

    What were you trying to do?

    Attempt to generate a custom check with the name of an existing file

    $ mix credo gen.check lib/test01.ex # first attempt works as expected, creates li/test01.ex
    $ mix credo gen.check lib/test01.ex # second attempt prints misleading error message
    

    Expected outcome

    Expected error message

    File exists: lib/test01.ex, aborted.
    

    Actual outcome

    Actual error message

    Please provide a filename:
    
      mix credo gen.check lib/my_first_credo_check.ex
    
    
    Reply
  • "Space missing after comma" and "no whitespace around parentheses/brackets most of the time" for interpolations and commas inside strings

    Jan 19, 2022

    Environment

    • Credo version (mix credo -v): 1.6.2
    • Erlang/Elixir version (elixir -v): Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit] Elixir 1.13.0 (compiled with Erlang/OTP 24)
    • Operating system: macOS Catalina v10.15.7

    What were you trying to do?

    Run mix credo --strict in a project with these line of codes:

    defp something do
      "something[#{:hey}]" #<- 1st warning
      String.split("testing,123", ",") #<- 2nd warning
    end
    

    Expected outcome

    No warning

    Actual outcome

    1st warning [C] ↗ There is no whitespace around parentheses/brackets most of ┃ the time, but here there is.

    2nd warning [R] → Space missing after comma

    Reply
  • Credo crashes with httpc error when starting
    Credo crashes with httpc error when starting

    Nov 11, 2016

    Environment

    • Credo version (mix credo -v): Unable to run as credo crashes but latest at the time of writing (cloned from git repo).
    • Erlang/Elixir version (elixir -v): Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace], Elixir 1.3.4
    • Operating system: Mac OS X 10.11.6

    What where you trying to do?

    Run mix credo.

    Expected outcome

    It runs.

    Actual outcome

    $ mix credo
    ** (exit) exited in: :gen_server.call(:httpc_manager, {:request, {:request, :undefined, #PID<0.70.0>, 0, :https, {'hex.pm', 443}, '/api/packages/credo', [], :get, {:http_request_h, :undefined, 'keep-alive', :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, 'application/vnd.hex+erlang', :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, 'hex.pm', :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, [], 'Credo/0.5.2 (Elixir/1.3.4) (OTP/19)', :undefined, :undefined, :undefined, '0', :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, ...}, {[], []}, {:http_options, 'HTTP/1.1', :infinity, true, {:essl, []}, :undefined, false, :infinity, false}, 'https://hex.pm/api/packages/credo', [], :none, [], 1478865591843, :undefined, :undefined, false}}, :infinity)
        ** (EXIT) no process
        (stdlib) gen_server.erl:212: :gen_server.call/3
        httpc.erl:574: :httpc.handle_request/9
        lib/credo/check_for_updates.ex:66: Credo.CheckForUpdates.fetch/1
        lib/credo/check_for_updates.ex:59: Credo.CheckForUpdates.fetch_all_hex_versions/1
        lib/credo/check_for_updates.ex:4: Credo.CheckForUpdates.run/0
        lib/credo/cli.ex:92: Credo.CLI.run/1
        lib/credo/cli.ex:58: Credo.CLI.main/1
        (mix) lib/mix/task.ex:296: Mix.Task.run_task/3
    
    Reply
  • `no function clause matching in Credo.Sources.find/1` error
    `no function clause matching in Credo.Sources.find/1` error

    Aug 18, 2016

    Followed the instructions in the README under 'Using Credo as stand alone'. I have a directory with a .exs file in it. It seems no matter what arguments I provide on the command-line, I get:

    $ mix credo word_count.exs
    ** (FunctionClauseError) no function clause matching in Credo.Sources.find/1
        lib/credo/sources.ex:19: Credo.Sources.find([])
        lib/credo/cli.ex:102: Credo.CLI.require_requires/1
        lib/credo/cli.ex:94: Credo.CLI.run/1
        lib/credo/cli.ex:58: Credo.CLI.main/1
        (mix) lib/mix/task.ex:296: Mix.Task.run_task/3
        (mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2
    

    This is using the tip of master (98cfdb089cb434d457cbf9ef6ac8be19a43f3d89). When I use the v0.4.8 tag, it works fine.

    Kind: Bug Status: help wanted 
    Reply
  • Check that the filename and module are consistent
    Check that the filename and module are consistent

    Oct 1, 2018

    This closes #568. @rrrene / @axelson I'd love to get your feedback on this. I tried to keep this as simple as possible.

    Reply
  • Sublime text plugin
    Sublime text plugin

    Dec 25, 2015

    This is a great tool!

    Is there anything in the works around getting a sublime / atom linter setup?

    Kind: Feature Level: Intermediate 
    Reply
  • Credo runs really slowly on my project
    Credo runs really slowly on my project

    May 24, 2016

    We've got a pretty large umbrella project (with 41 sub-apps), and I'm considering adding credo to the project and using a particular strict configuration (for the rules we care most about) as part of our travis CI build process. However, it's running reaaaaally slow:

    Analysis took 404.3 seconds (1.2s to load, 403.1s running checks)
    2432 mods/funs, found 7 consistency issues, 37 warnings, 1 code readability issue.
    

    That's nearly 7 minutes. Our Travis build currently has a few matrix entries that each run in the 2-4 minute range, so I'm not too keen to add another entry that takes 7 minutes as it'll effectively double our build times.

    Is there a way to configure things to speed credo up? Or are there plans to improve its perf?

    I already tried decreasing the number of enabled checks (commenting out most of them from my .credo.exs file) but that didn't appear to help.

    Reply
  • Bug: Pefer lazy Logger calls should take in consideration compile_time_purge_level: :debug option for :logger
    Bug: Pefer lazy Logger calls should take in consideration compile_time_purge_level: :debug option for :logger

    Apr 13, 2018

    Configuration

    # This file is responsible for configuring your application
    # and its dependencies with the aid of the Mix.Config module.
    use Mix.Config
    
    config :logger,
           level: :info,
           compile_time_purge_level: :debug
    

    Issue

    When using the compile_time_purge_level: :debug option, all Logger calls matching it are REMOVED from the source at compile time.

    Credo should take this option in consideration before issuing warnings.

    Environment

    • Credo version (mix credo -v): 0.9.1
    • Erlang/Elixir version (elixir -v): Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

    Elixir 1.6.3 (compiled with OTP 19)

    • Operating system: Windows 10 Pro 64 Bit

    What were you trying to do?

    Running mix credo --strict

    Expected outcome

    No warnings

    Actual outcome

    ➜  json git:(develop) ✗ mix credo --strict
    ➜  json git:(develop) ✗ mix credo --strict
    Checking 17 source files ...
    
      Warnings - please take a look
    ┃
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:80 #(JSON.Parser.Array.parse_array_contents)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:77 #(JSON.Parser.Array.parse_array_contents)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:71 #(JSON.Parser.Array.parse_array_contents)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:68 #(JSON.Parser.Array.parse_array_contents)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:61 #(JSON.Parser.Array.parse_array_contents)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:55 #(JSON.Parser.Array.parse_array_contents)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:49 #(JSON.Parser.Array.parse_array_contents)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:43 #(JSON.Parser.Array.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:39 #(JSON.Parser.Array.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser/array.ex:34 #(JSON.Parser.Array.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:108 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:104 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:100 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:96 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:92 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:88 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:84 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:80 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:76 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/parser.ex:72 #(JSON.Parser.parse)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:97 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:94 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:91 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:55 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:52 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:51 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:48 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:45 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json/decoder.ex:39 #(JSON.Decoder.DefaultImplementations.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:93 #(JSON.decode!)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:90 #(JSON.decode!)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:87 #(JSON.decode!)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:84 #(JSON.decode!)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:67 #(JSON.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:64 #(JSON.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:61 #(JSON.decode)
    ┃ [W] ↗ Prefer lazy Logger calls.
    ┃       lib/json.ex:58 #(JSON.decode)
    
    Please report incorrect results: https://github.com/rrrene/credo/issues
    
    Reply