## File: README.md # Credo [](https://github.com/rrrene/credo/actions?query=branch%3Amaster) [](https://hexdocs.pm/credo) 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. ## Installation and Usage The easiest way to add Credo to your project is by [using Mix](http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html). Add `:credo` as a dependency to your project's `mix.exs`: ```elixir defp deps do [ {:credo, "~> 1.7", only: [:dev, :test], runtime: false} ] end ``` And run: $ mix deps.get $ mix credo ## Documentation Documentation is [available on Hexdocs](https://hexdocs.pm/credo/) ## Integrations ### IDE/Editor Some IDEs and editors are able to run Credo in the background and mark issues inline. * [IntelliJ Elixir](https://github.com/KronicDeth/intellij-elixir#credo) - Elixir plugin for JetBrains IDEs (IntelliJ IDEA, Rubymine, PHPStorm, PyCharm, etc) * [linter-elixir-credo](https://atom.io/packages/linter-elixir-credo) - Package for Atom editor (by @smeevil) * [Elixir Linter (Credo)](https://marketplace.visualstudio.com/items?itemName=pantajoe.vscode-elixir-credo) - VSCode extension (by @pantajoe) * [flycheck](https://www.flycheck.org/en/latest/languages.html#elixir) - Emacs syntax checking extension * [kakoune](https://github.com/mawww/kakoune/wiki/Lint#elixir) - Config for linting support in Kakoune editor * [Neovim via null-ls](https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/lua/null-ls/builtins/diagnostics/credo.lua) - diagnostics builtin ### Automated Code Review * [Codacy](https://www.codacy.com/) - checks your code from style to security, duplication, complexity, and also integrates with coverage. ## Contributing 1. [Fork it!](http://github.com/rrrene/credo/fork) 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. --- ## File: guides/plugins/creating_plugins.md # Creating Plugins ## Basics A plugin is basically just a module that provides an `init/1` callback, taking a `Credo.Execution` struct as its only parameter and returning a `Credo.Execution`. That's basically it. ```elixir defmodule CredoDemoPlugin do def init(exec) do # but what do we do here?? exec end end ``` The `Credo.Plugin` module provides a number of functions for extending Credo's core features. ```elixir defmodule CredoDemoPlugin do @config_file File.read!(".credo.exs") import Credo.Plugin def init(exec) do exec |> register_default_config(@config_file) |> register_command("demo", CredoDemoPlugin.DemoCommand) |> register_cli_switch(:castle, :string, :X) |> append_task(:convert_cli_options_to_config, CredoDemoPlugin.ConvertCliSwitchesToPluginParams) |> prepend_task(:set_default_command, CredoDemoPlugin.SetDemoAsDefaultCommand) end end ``` You can find more information on `Credo.Plugin` and the functions imported: - `Credo.Plugin.register_default_config/2` - `Credo.Plugin.register_command/3` - `Credo.Plugin.register_cli_switch/5` - `Credo.Plugin.append_task/3` - `Credo.Plugin.append_task/4` - `Credo.Plugin.prepend_task/3` - `Credo.Plugin.prepend_task/4` ## Development Plugins are generally developed by putting them in a Hex package, referencing that package in `mix.exs` and then configuring the plugin in `.credo.exs`. However, for local development it can be beneficial to develop a plugin inside a project. But referencing modules from the current Mix project in `.credo.exs` does not work out of the box, because the project is not loaded for every `mix` task. To be able to use a module from the current project in `.credo.exs`, run `mix app.config` first: ```bash mix do app.config + credo ``` This way, local plugins can be referenced in `.credo.exs`. Another, even more pragmatic way to do it is to run `app.config` from `.credo.exs` directly (since it is just a script file): ```elixir Mix.Task.run("app.config", []) %{ configs: [ %{ name: "default", plugins: [ {MyProject.CredoPlugin, []} ] } ] } ``` This should naturally taken with a grain of salt, e.g. taking steps that this is only active during development. ## Further reading The demo plugin used in the docs can be found on GitHub and Hex: - https://github.com/rrrene/credo_demo_plugin - https://hex.pm/packages/credo_demo_plugin --- ## File: guides/plugins/using_plugins.md # Using Plugins ### Installing plugins Plugins are just modules. Plugins can be included by listing them under the `:plugins` field `.credo.exs`: ```elixir %{ configs: [ %{ name: "default", plugins: [ {CredoDemoPlugin, []} ] } ] } ``` Most of the time, a Credo plugin will be published on Hex. You include it as a dependency in `mix.exs` like any other dependency: ```elixir {:credo_demo_plugin, "~> 0.1.0"}, ``` Plugins, like checks, are just modules and functions. They are enabled in Credo's configuration file `.credo.exs`, which you can generate via `mix credo gen.config`: ```bash $ mix credo gen.config * creating .credo.exs ``` The demo plugin adds a command called "demo": ```bash $ mix credo demo By the power of ! ``` It seems like there's something missing before the `!` ... ### Configuring plugins Plugins can be configured via params, just like checks. Each entry consists of a two-element tuple: the plugin's module and a keyword list of parameters, which can be used to configure the plugin itself. ```elixir %{ configs: [ %{ name: "default", plugins: [ {CredoDemoPlugin, [castle: "Grayskull"]} ] } ] } ``` ```bash $ mix credo demo By the power of Grayskull! ``` Just in case, Plugins can be deactivated by setting the second tuple element to `false`. ```elixir %{ configs: [ %{ name: "default", plugins: [ {CredoDemoPlugin, false} # <-- don't load this for now ] } ] } ``` The demo plugin used in the docs can be found on GitHub and Hex: - https://github.com/rrrene/credo_demo_plugin - https://hex.pm/packages/credo_demo_plugin --- ## File: guides/introduction/basic_usage.md # Basic Usage Since Credo is all about teaching people, you can find out more about that particular entry. ## Run Analysis To run credo in the current project, just type: ```bash $ mix credo ``` This will run the code analysis and suggest places to edit your code. ## Explain Issues Running Credo will yield issues like this: ```bash ┃ Refactoring opportunities ┃ ┃ [F] ↗ Avoid negated conditions in if-else blocks. ┃ lib/foo/bar.ex:306 #(Foo.Bar.deprecated_def_explanations) ``` To find out more about the issue, just append its location to the `credo` command: ```bash mix credo lib/foo/bar.ex:306 ``` The result is an explanation of the issue, including the check that raised the issue, its configuration options and how to disable it. ## Advanced Usage ### Strict Analysis Like any code linter, Credo reports issues. Contrary to many other linters these issues are not created equal. Each issue is assigned a priority, based on a base priority set by the config and a dynamic component based on violation severity and location in the source code. These priorities hint at the importance of each issue and are displayed in the command-line interface using arrows: ↑ ↗ → ↘ ↓ By default, only issues with a positive priority are part of the report (↑ ↗ →). To include all issues, just type: ```bash mix credo --strict ``` ### Format output as JSON Credo can provide the output of every command as JSON: ```bash $ mix credo lib/foo/bar.ex:306 --format json ``` ```json { "explanations": [ { "category": "refactor", "check": "Elixir.Foo.Bar.Refactor.NegatedConditionsWithElse", "column": null, "explanation_for_issue": "An `if` block with a negated condition should not contain an else block.\n\nSo while this is fine:\n\n if not allowed? do\n raise \"Not allowed!\"\n end\n\nThe code in this example ...\n\n if not allowed? do\n raise \"Not allowed!\"\n else\n proceed_as_planned()\n end\n\n... should be refactored to look like this:\n\n if allowed? do\n proceed_as_planned()\n else\n raise \"Not allowed!\"\n end\n\nThe same goes for negation through `!` instead of `not`.\n\nThe reason for this is not a technical but a human one. It is easier to wrap\nyour head around a positive condition and then thinking \"and else we do ...\".\n\nIn the above example raising the error in case something is not allowed\nmight seem so important to put it first. But when you revisit this code a\nwhile later or have to introduce a colleague to it, you might be surprised\nhow much clearer things get when the \"happy path\" comes first.\n", "filename": "lib/foo/bar.ex", "line_no": 306, "message": "Avoid negated conditions in if-else blocks.", "priority": 12, "related_code": [ [304, " explanation = Module.get_attribute(env.module, explanation)"], [305, ""], [306, " if not is_nil(explanation) do"], [307, " # deprecated - remove once we ditch @explanation"], [308, " quote do"] ], "scope": "Foo.Bar.deprecated_def_explanations", "trigger": "!" } ] } ``` ## Further reading If you are interested in more ways to configure Credo, continue reading in the following sections: * [CLI switches](../configuration/cli_switches.md) * [Configuration file](../configuration/config_file.md) --- ## File: guides/introduction/exit_statuses.md # Exit Status Credo succeeds with an exit status of 0 (like any other program). Credo fails with an exit status between 1 and 127 if it shows any issues. Exit statuses above or equal to 128 indicate an actual runtime error during analysis itself. This enables shell based pipeline workflows (e.g. on CI systems) which test Credo compliance. ## Issue Statuses The exit status of each check is used to construct a bit map of the types of issues which were encountered by or-ing them together to produce the final result: ```elixir import Bitwise issues |> Enum.map(&(&1.exit_status)) |> Enum.reduce(0, &(&1 ||| &2)) ``` This way you can reason about the encountered issues right from the exit status. Default values for the checks are based on their category: consistency: 1 design: 2 readability: 4 refactor: 8 warning: 16 Let's see what this means using an example: ```bash $ mix credo [...snip...] $ echo $? 12 ``` So an exit status of `12` tells you that you have only Readability Issues (`4`) and Refactoring Opportunities (`8`), but e.g. no Warnings. Naturally, custom checks and plugins can provide their own exit statuses. ```bash : 32 : 64 ``` ## Actual & Custom Errors To also allow for actual errors, an exit status of `>= 128` signals something went wrong during analysis itself. Since one cannot combine these, they do not follow the bitwise notation described above: ```bash Generic Credo error: 128 Credo Config errors: 129-131 Reserved errors: 132-191 ``` Naturally, plugins can provide their own exit statuses. ```bash : 192-255 ``` --- ## File: guides/introduction/installation.md # Installation The easiest way to add Credo to your project is by [using Mix](http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html). Add `:credo` as a dependency to your project's `mix.exs`: ```elixir defp deps do [ {:credo, "~> 1.7", only: [:dev, :test], runtime: false} ] end ``` And run: ```bash $ mix deps.get ``` ## Compatibility Credo aims to stay compatible with the list of [Elixir minor releases mentioned in the Elixir docs](https://hexdocs.pm/elixir/compatibility-and-deprecations.html). These are the releases that are actively tested on CI. Please note that Credo sometimes stays technically compatible with even earlier versions coincidentally. --- ## File: guides/introduction/mix_tasks.md # Mix Tasks After including Credo in a project's dependencies (see [Installation](../introduction/installation.md)), there are a number of built-in mix tasks available: ```bash $ mix help | grep -i credo mix credo # Run code analysis (use `--help` for options) mix credo.gen.check # Generate a new custom check for Credo mix credo.gen.config # Generate a new config for Credo ``` If you want to know more about `mix`, check out [Introduction to Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html). `mix credo` Runs Credo's analysis. Check out [Configuration](../configuration/cli_switches.md) on how to customize inputs and outputs. `mix credo.gen.check` Generates a custom Credo check. `mix credo.gen.config` Generates a Credo config file. Check out [Configuration](../configuration/config_file.md) on how to customize it. --- ## File: guides/introduction/overview.md # Overview Credo is a static code analysis tool for the Elixir language with a focus on teaching and code consistency. `credo` 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. The basic functionality will be familiar to you if you used a linter like JavaScript's [ESLint](https://eslint.org/), Ruby's [RuboCop](https://github.com/rubocop-hq/rubocop) or C#'s [Stylecop](https://github.com/StyleCop/StyleCop) before. Contrary to the aforementioned tools, Credo puts a strong emphasis on teaching and code consistency. To get started, you might want to check out these guides: * [Installation](../introduction/installation.md) * [Basic usage](../introduction/basic_usage.md) * [Configuration via `.credo.exs`](../configuration/config_file.md) Once you are familiar with the basics, look into these topics: * [Adding custom checks](../custom_checks/adding_checks.md) * [Testing custom checks](../custom_checks/testing_checks.md) * [Plugins](../plugins/creating_plugins.md) Any incorrect or unclear information in these docs should be considered a bug. In case you find something, you can help maintaining these docs by [filing an issue on GitHub](https://github.com/rrrene/credo/issues) and/or opening a pull request. --- ## File: guides/custom_checks/adding_checks.md # Adding Custom Checks There comes a time when Credo does not feature the check you need or where you want to test a project- or domain-specific aspect of your codebase. This is when you should consider implementing a Custom Check. Custom checks are simply modules implementing the `Credo.Check` behaviour, which most of the time means that it is a module with a `run/2` function returning a list of `Credo.Issue` structs: # lib/checks/my_check.ex defmodule MyProject.Checks.MyCheck do use Credo.Check def run(%SourceFile{} = source_file, params) do # end end Check `Credo.Check` for more technical information. ### Our first check: Policing module attributes Sometimes the conventions for names of module attributes change within a development team and you want to encourage people to stop using the old naming scheme for module attributes to avoid endless bikeshedding about whether or not the new naming policy was needed in the first place. So, let's implement a check for this completely made up scenario! ### Minimal check & config First, we add the necessary Elixir module for the check and, for now, just return an empty list of issues. ```elixir # lib/my_project/checks/reject_module_attributes.ex defmodule MyProject.Checks.RejectModuleAttributes do # Set up the behaviour and make this module a "check": use Credo.Check # The minimum each check has to implement is a `run/2` function which returns the found issues: def run(source_file, params \\ []) do [] end end ``` To run our new check, we also need to add the necessary `:requires` and `:checks` in our Credo config file: ```elixir # .credo.exs %{ configs: [ %{ name: "default", requires: ["./lib/my_project/checks/**/*.ex"], checks: [ {MyProject.Checks.RejectModuleAttributes, []} ] } ] } ``` This tells Credo to require all files in our checks directory and to enable the `RejectModuleAttributes` check. ### Getting it working For a first implementation, our check should look into all modules and report "violations" against a list of rejected names. ```elixir # lib/my_project/checks/reject_module_attributes.ex defmodule MyProject.Checks.RejectModuleAttributes do use Credo.Check # Let's say we want to report module attributes named `@checkdoc` @rejected_names [:checkdoc] def run(source_file, params \\ []) do # IssueMeta helps keeping track of the source file and the check's params # (technically, it's just a custom tagged tuple) issue_meta = IssueMeta.for(source_file, params) # we'll walk the `source_file`'s AST and look for module attributes matching `@rejected_names` Credo.Code.prewalk(source_file, &traverse(&1, &2, @rejected_names, issue_meta)) end # This matches on the AST structure of module attributes. defp traverse({:@, _, [{name, meta, [_string]} | _]} = ast, issues, rejected_names, issue_meta) do if Enum.member?(rejected_names, name) do {ast, issues ++ [issue_for(name, meta[:line], issue_meta)]} else {ast, issues} end end # For all AST nodes not matching the pattern above, we simply do nothing: defp traverse(ast, issues, _rejected_names, _issue_meta) do {ast, issues} end defp issue_for(trigger, line_no, issue_meta) do format_issue( issue_meta, message: "There should be no `@#{trigger}` module attributes.", trigger: "@#{trigger}", line_no: line_no ) end end ``` Traversal of the AST is done via `Credo.Code.prewalk/2`, which is a light wrapper around `Macro.prewalk/3`, taking a `Credo.SourceFile` struct instead of an AST. You can use `Code.string_to_quoted!/1` to look at the AST structure for code snippets: ```elixir iex> Code.string_to_quoted!("@my_attribute 23") {:@, [line: 1], [{:my_attribute, [line: 1], [23]}]} ``` ### Adding configuration parameters Next, we should have a config parameter which allows us to define which module attribute names are no longer allowed. ```elixir defmodule MyProject.Checks.RejectModuleAttributes do # To add a parameter, we use the `:param_defaults` keyword with `use Credo.Check`: use Credo.Check, param_defaults: [reject: [:checkdoc]] def run(source_file, params \\ []) do # To get a parameter, we use `Params.get/3`, which returns the given parameter from the config # or the default we registered above: reject = Params.get(params, :reject, __MODULE__) issue_meta = IssueMeta.for(source_file, params) Credo.Code.prewalk(source_file, &traverse(&1, &2, reject, issue_meta)) end # ... end ``` We can now use `.credo.exs` to configure the `:reject` param. If the param is not declared ... {MyProject.Checks.RejectModuleAttributes, []} ... then the default from your check is used. If the param is declared, it overwrites the default, meaning that this ... {MyProject.Checks.RejectModuleAttributes, [reject: [:shortdoc]]} ... forbids `@shortdoc`, but allows `@checkdoc` again. Our final `.credo.exs` might look something like this: ```elixir # .credo.exs %{ configs: [ %{ name: "default", requires: ["./lib/my_project/checks/**/*.ex"], checks: [ {MyProject.Checks.RejectModuleAttributes, [reject: [:checkdoc, :other_attr]]} ] } ] } ``` ### Finalizing the check To really make this a full-fledged Credo check, we have to configure its priority, category and describe what it does (you can find a description of the options in `Credo.Check`). ```elixir defmodule MyProject.Checks.RejectModuleAttributes do use Credo.Check, base_priority: :high, category: :readability, param_defaults: [reject: []], explanations: [ check: """ Look, sometimes the policies for names of module attributes change. We want to make sure that all module attributes adhere to the newest standards of ACME Corp. We do not want to discuss this policy, we just want to stop you from using the old module attributes :) """, params: [reject: "This check warns about module attributes with any of the given names."] ] # ... end ``` You can now use Credo's `explain` command ... ```bash $ mix credo explain MyProject.Checks.RejectModuleAttributes ``` ... to show a description of your new check: ```bash MyProject.Checks.MyIExPry ┃ ┃ [R] Category: readability ┃ ↗ Priority: high ┃ ┃ __ WHY IT MATTERS ┃ ┃ Look, sometimes the policies for names of module attributes change. ┃ We want to make sure that all module attributes adhere to the newest standards of ACME Corp. ┃ ┃ We do not want to discuss this policy, we just want to stop you from using the old ┃ module attributes :) ┃ ┃ __ CONFIGURATION OPTIONS ┃ ┃ To configure this check, use this tuple ┃ ┃ {MyProject.Checks.RejectModuleAttributes, } ┃ ┃ with being false or any combination of these keywords: ┃ ┃ reject: Names of module attributes that are no longer allowed ┃ (defaults to []) ┃ ``` And that's it. Here's the final check: ```elixir defmodule MyProject.Checks.RejectModuleAttributes do use Credo.Check, base_priority: :high, category: :readability, param_defaults: [reject: []], explanations: [ check: """ Look, sometimes the policies for names of module attributes change. We want to make sure that all module attributes adhere to the newest standards of ACME Corp. We do not want to discuss this policy, we just want to stop you from using the old module attributes :) """, params: [reject: "Names of module attributes that are no longer allowed"] ] def run(source_file, params \\ []) do reject = Params.get(params, :reject, __MODULE__) issue_meta = IssueMeta.for(source_file, params) Credo.Code.prewalk(source_file, &traverse(&1, &2, reject, issue_meta)) end defp traverse({:@, _, [{name, meta, [_string]} | _]} = ast, issues, rejected_names, issue_meta) do if Enum.member?(rejected_names, name) do {ast, issues ++ issue_for(name, meta[:line], issue_meta)} else {ast, issues} end end defp traverse(ast, issues, _rejected_names, _issue_meta) do {ast, issues} end defp issue_for(trigger, line_no, issue_meta) do format_issue( issue_meta, message: "There should be no `@#{trigger}` module attributes.", trigger: trigger, line_no: line_no ) end end ``` Next, let's see how we can [write tests for our custom check!](./testing_checks.md) --- ## File: guides/custom_checks/improving_checks.md # Improving Custom Checks ```elixir defmodule Credo.Check.Readability.DuplicatedAliases do use Credo.Check, base_priority: :low, category: :readability, explanations: [ check: """ Sometimes during code reviews in large projects with modules that use many aliases, there can be issues when solving conflicts and some duplicated may end up not being noticed by reviewers and get merged into the main branch. These duplicated alias can accumulate over many different files over time and make the aliases section of a file larger and more confusing. """ ] alias Credo.SourceFile def run(source_file, params \\ []) do issue_meta = IssueMeta.for(source_file, params) source_ast = SourceFile.ast(source_file) {_, {_, _, issues}} = Macro.prewalk(source_ast, {%{}, issue_meta, []}, &traverse(&1, &2)) issues end defp traverse( {:alias, _, [{:__aliases__, meta, aliased_module} | _]} = ast, {cache, issue_meta, issues} ) do if Map.has_key?(cache, aliased_module) do existing_alias_meta = Map.fetch!(cache, aliased_module) issue = build_issue(Credo.Code.Name.full(aliased_module), meta[:line], existing_alias_meta[:line], issue_meta) {ast, {cache, issue_meta, [issue | issues]}} else {ast, {Map.put(cache, aliased_module, meta), issue_meta, issues}} end end defp traverse(ast, acc), do: {ast, acc} defp build_issue(trigger, line_no, existing_alias_line_no, issue_meta) do format_issue( issue_meta, message: "Duplicated alias: #{trigger}, already defined in line #{existing_alias_line_no}", trigger: trigger, line_no: line_no ) end end ```