Recode
A linter with autocorrection and a refactoring tool.
Install / Use
/learn @hrzndhrn/RecodeREADME
Recode
A linter with autocorrection.
recode is an experimental project to play around with the great
sourceror package by @doorgan.
This library is still under development, breaking changes are expected.
recode can correct and check the following things:
> mix recode.help
Design tasks:
TagFIXME # Checker - Checks if there are FIXME tags in the sources.
TagTODO # Checker - Checks if there are TODO tags in the sources.
Readability tasks:
AliasExpansion # Corrector - Expands multi aliases to separate aliases.
AliasOrder # Corrector - Checks if aliases are sorted alphabetically.
EnforceLineLength # Corrector - Forces expressions to one line.
Format # Corrector - Does the same as `mix format`.
LocalsWithoutParens # Corrector - Removes parens from locals without parens.
Moduledoc # Checker - There should be a @moduledoc in any module.
PipeFunOne # Corrector - Add parentheses to one-arity functions.
SinglePipe # Corrector - Pipes should only be used when piping data through multiple calls.
Specs # Checker - Checks for specs.
UnnecessaryIfUnless # Corrector - Removes redundant booleans
Refactor tasks:
FilterCount # Corrector - Checks calls like Enum.filter(...) |> Enum.count().
Nesting # Checker - Checks code nesting depth in functions and macros.
Warning tasks:
Dbg # Corrector - There should be no calls to dbg.
IOInspect # Corrector - There should be no calls to IO.inspect.
TestFile # Corrector - Checks the file extension of test files.
UnusedVariable # Corrector - Checks if unused variables occur.
It is also possible to run recode in a none-autocorrect mode to just lint your
code.
Installation
The package can be installed by adding recode to your list of dependencies
in mix.exs:
def deps do
[
{:recode, "~> 0.7", only: :dev, runtime: false}
]
end
Recode requires Elixir 1.13.0 or higher. If you add recode to a project that
supports lower Elixir versions you could add recode as following:
def deps do
[
# your deps
] ++ recode()
end
defp recode() do
case Version.match?(System.version(), "~> 1.13") do
true -> [{:recode, "~> 0.7", only: :dev, runtime: false}]
false -> []
end
end
Documentation can be found at https://hexdocs.pm/recode.
Usage
To start with recode a configuration file is needed.
mix recode.gen.config
This mix task generates the config file .recode.exs.
[
version: "0.7.0",
# Can also be set/reset with `--autocorrect`/`--no-autocorrect`.
autocorrect: true,
# With "--dry" no changes will be written to the files.
# Can also be set/reset with `--dry`/`--no-dry`.
# If dry is true then verbose is also active.
dry: false,
# Can also be set/reset with `--verbose`/`--no-verbose`.
verbose: false,
# Can be overwritten by calling `mix recode "lib/**/*.ex"`.
inputs: ["{mix,.formatter}.exs", "{apps,config,lib,test}/**/*.{ex,exs}"],
formatters: [Recode.CLIFormatter],
tasks: [
# Tasks could be added by a tuple of the tasks module name and an options
# keyword list. A task can be deactivated by `active: false`. The execution of
# a deactivated task can be forced by calling `mix recode --task ModuleName`.
{Recode.Task.AliasExpansion, []},
{Recode.Task.AliasOrder, []},
{Recode.Task.Dbg, [autocorrect: false]},
{Recode.Task.EnforceLineLength, [active: true, exclude: "mix.exs"]},
{Recode.Task.FilterCount, []},
{Recode.Task.IOInspect, [autocorrect: false]},
{Recode.Task.Nesting, []},
{Recode.Task.PipeFunOne, []},
{Recode.Task.SinglePipe, []},
{Recode.Task.Specs, [exclude: "test/**/*.{ex,exs}", config: [only: :visible]]},
{Recode.Task.TagFIXME, [exit_code: 2]},
{Recode.Task.TagTODO, [exit_code: 4]},
{Recode.Task.TestFile, []},
{Recode.Task.UnnecessaryIfUnless, []},
{Recode.Task.UnusedVariable, [active: false]}
]
]
If a configuration file already exists, you can use the mix task
mix recode.update.config
to update the configuration file.
mix recode
This mix task runs the linter with autocorrection. The switch --dry (alias
-d) prevents the update of the files and shows all changes in the console.
> cd examples/my_code
> mix recode --dry --no-color --no-manifest
Read 24 files in 0.01s
...................................................!...............!!.........................................................................!....!..!................................!........!...............!..!...!..................!....!.....!................!........!.....!...............!...!!..!.........!...!.!........!!..!..!!!.!....
File: .formatter.exs
Updates: 1
Changed by: Format
...|
13 13 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/**/*.json"],
14 14 | locals_without_parens: [noop: 1],
15 - | subdirectories: ["priv/*/migrations"],
15 + | subdirectories: ["priv/*/migrations"]
16 16 |]
17 17 |
File: lib/my_code.ex
[Specs 15/3] Functions should have a @spec type specification.
File: lib/my_code/alias_expansion.ex
Updates: 1
Changed by: AliasExpansion
1 1 |defmodule MyCode.AliasExpansion do
2 - | alias MyCode.{PipeFunOne, SinglePipe}
2 + | alias MyCode.PipeFunOne
3 + | alias MyCode.SinglePipe
3 4 |
4 5 | def foo(x) do
...|
[Moduledoc 1/1] The module Elixir.MyCode.AliasExpansion is missing @moduledoc.
[Specs 5/3] Functions should have a @spec type specification.
File: lib/my_code/alias_order.ex
Updates: 2
Changed by: AliasOrder, AliasExpansion
...|
12 12 |
13 13 |defmodule Mycode.AliasOrder do
14 - | alias MyCode.SinglePipe
14 + | alias MyCode.Echo
15 + | alias MyCode.Foxtrot
15 16 | alias MyCode.PipeFunOne
16 - | alias MyCode.{Foxtrot, Echo}
17 + | alias MyCode.SinglePipe
17 18 |
18 19 | @doc false
...|
[Moduledoc 13/1] The module Elixir.Mycode.AliasOrder is missing @moduledoc.
File: lib/my_code/deep.ex
[Moduledoc 1/1] The module Elixir.MyCode.Deep is missing @moduledoc.
[Specs 2/3] Functions should have a @spec type specification.
[Nesting 6/11] The body is nested too deep (max depth: 2).
File: lib/my_code/multi.ex
Updates: 4
Changed by: SinglePipe, PipeFunOne, FilterCount, Format
1 1 |defmodule MyCode.Multi do
2 - |
3 2 | import MyCode.Fun
4 3 |
...|
6 5 |
7 6 | def pipe(x) do
8 - | x |> double |> double() |> dbg()
7 + | x |> double() |> double() |> dbg()
9 8 | end
10 9 |
11 10 | def single(x) do
12 - | x |> double()
11 + | double(x)
13 12 | end
14 13 |
...|
19 18 | def my_count(list) do
20 19 | list
21 - | |> Enum.filter(fn x -> rem(x, 2) == 0 end)
22 - | |> Enum.count()
20 + | |> Enum.count(fn x -> rem(x, 2) == 0 end)
23 21 | |> IO.inspect()
24 22 | end
...|
[Moduledoc 1/1] The module Elixir.MyCode.Multi is missing @moduledoc.
[Specs 4/3] Functions should have a @spec type specification.
[Specs 6/3] Functions should have a @spec type specification.
[Dbg 7/34] There should be no calls to dbg.
[Specs 10/3] Functions should have a @spec type specification.
[Specs 14/3] Functions should have a @spec type specification.
[Specs 18/3] Functions should have a @spec type specification.
[IOInspect 21/8] There should be no calls to IO.inspect.
File: lib/my_code/pipe_fun_one.ex
Updates: 1
Changed by: PipeFunOne
...|
5 5 |
6 6 | def pipe(x) do
7 - | x |> double |> double()
7 + | x |> double() |> double()
8 8 | end
9 9 |end
...|
File: lib/my_code/same_line.ex
[Moduledoc 1/1] The module Elixir.MyCode.SameLine is missing @moduledoc.
[Specs 2/3] Functions should have a @spec type specification.
File: lib/my_code/single_pipe.ex
Updates: 1
Changed by: SinglePipe
...|
5 5 |
6 6 | def single_pipe(x) do
7 - | x |> double()
7 + | double(x)
8 8 | end
9 9 |
10 - | def reverse(a), do: a |> Enum.reverse()
10 + | def reverse(a), do: Enum.reverse(a)
11 11 |end
12 12 |
File: lib/my_code/tags.ex
[TagTODO 3/-] Found a tag: TODO: add docs
[TagFIXME 6/-] Found a tag: FIXME: add more functions
[Specs 7/3] Functions should have a @spec type specification.
File: lib/my_code/trailing_comma.ex
Updates: 2
Changed by: SinglePipe, Format
...|
3 3 |
4 4 | def list do
5 - | [
5 + | Enum.reverse([
6 6 | 100_000,
7 7 | 200_000,
...|
14 14 | 900_000,
15 15 | 1_000_000,
16 - | 2_000_000,
17 - | ] |> Enum.reverse()
16 + | 2_000_000
17 + | ])
18 18 | end
19 19 |end
...|
File: mix.exs
[Moduledoc 1/1] The module Elixir.MyCode.MixProject is missing @moduledoc.
File: priv/repo/migrations/20190417140000_create_users.exs
Updates: 2
Changed by: LocalsWithoutParens, Format
...|
4 4 | def up do
5 5 | create table("users") do
6 - | add :first_name, :string, size: 40
7 - | add(:last_name, :string, size: 40)
6 + | add :first_name, :string, size: 40
7 + | add :last_name, :string, size: 40
8 8 |
9 9 | timestamps()
...|
[Moduledoc 1/1] The module Elixir.MyRepo.Migrations.CreateUsers is missing @moduledoc.
[Specs 4/3] Functions should have a @spec type specific
