From 8d1ee1c9f005ee2494ab8ed1a4a260284b4f20f5 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 10 Nov 2024 13:58:03 +0100 Subject: [PATCH 01/25] Changes for rewrite 1.0.0 (#96) --- lib/recode/task/filter_count.ex | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/recode/task/filter_count.ex b/lib/recode/task/filter_count.ex index 2933bbe..efbc562 100644 --- a/lib/recode/task/filter_count.ex +++ b/lib/recode/task/filter_count.ex @@ -36,10 +36,6 @@ defmodule Recode.Task.FilterCount do |> update(source, opts) end - defp update({zipper, _issues}, source, true) do - Source.update(source, :quoted, Zipper.root(zipper), by: __MODULE__) - end - defp update({zipper, issues}, source, opts) do update_source(source, opts, quoted: zipper, issues: issues) end From 3d8ae5c7173fe48a5740edecff939ce7332dfc33 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 10 Nov 2024 15:19:51 +0100 Subject: [PATCH 02/25] Read inputs from .formatter.exs (#98) --- examples/my_code/.recode.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/my_code/.recode.exs b/examples/my_code/.recode.exs index 47bfeb0..2bd6143 100644 --- a/examples/my_code/.recode.exs +++ b/examples/my_code/.recode.exs @@ -10,8 +10,11 @@ color: true, # Can also be set/reset with `--verbose`/`--no-verbose`. verbose: false, +<<<<<<< HEAD # Can be overwritten with `--silent`/`--no-silent`. silent: false, +======= +>>>>>>> 41b835b (Read inputs from .formatter.exs (#98)) # Inputs can be a path, glob expression or list of paths and glob expressions. # With the atom :formatter the inputs from .formatter.exs are # used. also allowed in the list mentioned above. @@ -42,4 +45,4 @@ {Recode.Task.UnnecessaryIfUnless, []}, {Recode.Task.UnusedVariable, [active: false]} ] -] +] \ No newline at end of file From f9f50276130bb4fd89f2738e306758a733d5cda9 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 10 Nov 2024 15:44:29 +0100 Subject: [PATCH 03/25] Add recode.issues manifest (#99) --- lib/recode/runner/impl.ex | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/lib/recode/runner/impl.ex b/lib/recode/runner/impl.ex index 62aebf5..99c4116 100644 --- a/lib/recode/runner/impl.ex +++ b/lib/recode/runner/impl.ex @@ -1,3 +1,55 @@ +defmodule Recode.Manifest do + @moduledoc false + + alias Rewrite.Source + + @manifest "recode.issues" + + def write(project, config) do + if config[:manifest] do + File.write(path(), content(project, config[:dry])) + else + :ok + end + end + + def read(config) do + if !config[:force] and config[:manifest] do + case File.read(path()) do + {:ok, content} -> {timestamp(), String.split(content, "\n")} + _error -> nil + end + else + nil + end + end + + def timestamp do + case File.stat(path(), time: :posix) do + {:ok, %{mtime: timestamp}} -> timestamp + {:error, _reason} -> 0 + end + end + + def path, do: Path.join(Mix.Project.manifest_path(), @manifest) + + defp content(project, dry) do + project + |> paths_with_issue(dry) + |> Enum.join("\n") + end + + defp paths_with_issue(project, dry) do + Enum.reduce(project, [], fn source, acc -> + if Source.has_issues?(source) or (dry and Source.updated?(source)) do + [source.path | acc] + else + acc + end + end) + end +end + defmodule Recode.Runner.Impl do @moduledoc false From 67a34b9517c90c33401387b170a149a06b7bace3 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Fri, 15 Nov 2024 16:36:47 +0100 Subject: [PATCH 04/25] Update manifest handling --- lib/recode/runner/impl.ex | 52 --------------------------------------- 1 file changed, 52 deletions(-) diff --git a/lib/recode/runner/impl.ex b/lib/recode/runner/impl.ex index 99c4116..62aebf5 100644 --- a/lib/recode/runner/impl.ex +++ b/lib/recode/runner/impl.ex @@ -1,55 +1,3 @@ -defmodule Recode.Manifest do - @moduledoc false - - alias Rewrite.Source - - @manifest "recode.issues" - - def write(project, config) do - if config[:manifest] do - File.write(path(), content(project, config[:dry])) - else - :ok - end - end - - def read(config) do - if !config[:force] and config[:manifest] do - case File.read(path()) do - {:ok, content} -> {timestamp(), String.split(content, "\n")} - _error -> nil - end - else - nil - end - end - - def timestamp do - case File.stat(path(), time: :posix) do - {:ok, %{mtime: timestamp}} -> timestamp - {:error, _reason} -> 0 - end - end - - def path, do: Path.join(Mix.Project.manifest_path(), @manifest) - - defp content(project, dry) do - project - |> paths_with_issue(dry) - |> Enum.join("\n") - end - - defp paths_with_issue(project, dry) do - Enum.reduce(project, [], fn source, acc -> - if Source.has_issues?(source) or (dry and Source.updated?(source)) do - [source.path | acc] - else - acc - end - end) - end -end - defmodule Recode.Runner.Impl do @moduledoc false From 6c57bdd481ab7ae505477b8a041692b6f169c0ed Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 19 Nov 2024 07:37:07 +0100 Subject: [PATCH 05/25] Create issues for unformatted files in dry mode This feature is already part of recode, but was lost in the development of the new version. --- examples/minimal/lib/minimal.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/minimal/lib/minimal.ex b/examples/minimal/lib/minimal.ex index 4b22897..4fc3f60 100644 --- a/examples/minimal/lib/minimal.ex +++ b/examples/minimal/lib/minimal.ex @@ -14,6 +14,6 @@ defmodule Minimal do """ @spec hello :: :world def hello do - :world + :mars end end From 591d9e5b1f00d1a17a824e76d020009ef1d8ea54 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 19 Nov 2024 08:13:26 +0100 Subject: [PATCH 06/25] Add minor refactorings --- examples/minimal/lib/minimal.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/minimal/lib/minimal.ex b/examples/minimal/lib/minimal.ex index 4fc3f60..4b22897 100644 --- a/examples/minimal/lib/minimal.ex +++ b/examples/minimal/lib/minimal.ex @@ -14,6 +14,6 @@ defmodule Minimal do """ @spec hello :: :world def hello do - :mars + :world end end From 4e5f47744878140ca313817ea589b166ed23fa3b Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 19 Nov 2024 09:34:06 +0100 Subject: [PATCH 07/25] Add silent mode (#100) * Add silent mode * Remove negated condition in if-else * Add default for silent in CliFormatter * Update silent mode doc * Add some tests * Fix typo * Add tests * Fix rebase fail * Add minor refactorings --- examples/my_code/.recode.exs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/my_code/.recode.exs b/examples/my_code/.recode.exs index 2bd6143..47bfeb0 100644 --- a/examples/my_code/.recode.exs +++ b/examples/my_code/.recode.exs @@ -10,11 +10,8 @@ color: true, # Can also be set/reset with `--verbose`/`--no-verbose`. verbose: false, -<<<<<<< HEAD # Can be overwritten with `--silent`/`--no-silent`. silent: false, -======= ->>>>>>> 41b835b (Read inputs from .formatter.exs (#98)) # Inputs can be a path, glob expression or list of paths and glob expressions. # With the atom :formatter the inputs from .formatter.exs are # used. also allowed in the list mentioned above. @@ -45,4 +42,4 @@ {Recode.Task.UnnecessaryIfUnless, []}, {Recode.Task.UnusedVariable, [active: false]} ] -] \ No newline at end of file +] From 39d596fef7b84514e59d4505ca78c4bdf4c3064d Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Jan 2025 13:15:55 +0100 Subject: [PATCH 08/25] Fix LocalsWtihoutParens for multiline calls --- .../task/locals_without_parens_test.exs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/recode/task/locals_without_parens_test.exs b/test/recode/task/locals_without_parens_test.exs index 7fb2217..bf933f0 100644 --- a/test/recode/task/locals_without_parens_test.exs +++ b/test/recode/task/locals_without_parens_test.exs @@ -8,22 +8,21 @@ defmodule Recode.Task.LocalsWithoutParensTest do dot_formatter = DotFormatter.from_formatter_opts(locals_without_parens: [foo: 1, bar: 2, baz: :*]) + # code = """ + # [x] = foo(bar(y)) + # bar(y, x) + # baz(a) + # baz(a,b) + # baz(a,b,c) + # if(x == 1, do: true) + # """ + code = """ [x] = foo(bar(y)) - bar(y, x) - baz(a) - baz(a,b) - baz(a,b,c) - if(x == 1, do: true) """ expected = """ [x] = foo bar(y) - bar y, x - baz a - baz a, b - baz a, b, c - if x == 1, do: true """ code From 8a3e6cfdfdfaee3350d7b013d4483c7fcaad9453 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Jan 2025 13:21:52 +0100 Subject: [PATCH 09/25] Fix LocalsWtihoutParens in pipes --- lib/recode/task/locals_without_parens.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/recode/task/locals_without_parens.ex b/lib/recode/task/locals_without_parens.ex index 59bdf87..d080cc3 100644 --- a/lib/recode/task/locals_without_parens.ex +++ b/lib/recode/task/locals_without_parens.ex @@ -109,7 +109,7 @@ defmodule Recode.Task.LocalsWithoutParens do locals_without_parens, autocorrect? ) do - if remove_parens?(fun, meta, args, locals_without_parens) do + if remove_parens?(fun, meta, args, locals_without_parens) do if autocorrect? do node = {fun, Keyword.delete(meta, :closing), args} {:cont, Zipper.replace(zipper, node), issues} @@ -123,7 +123,7 @@ defmodule Recode.Task.LocalsWithoutParens do end defp remove_parens?(fun, meta, args, locals_without_parens) do - Keyword.has_key?(meta, :closing) and not multiline?(meta) and + Keyword.has_key?(meta, :closing) and not multiline?(meta) and local_without_parens?(locals_without_parens, fun, args) end From e2828e40c2684ec79d7664e188b080d36d3ec965 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Jan 2025 13:29:04 +0100 Subject: [PATCH 10/25] Run mix format --- lib/recode/task/locals_without_parens.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/recode/task/locals_without_parens.ex b/lib/recode/task/locals_without_parens.ex index d080cc3..59bdf87 100644 --- a/lib/recode/task/locals_without_parens.ex +++ b/lib/recode/task/locals_without_parens.ex @@ -109,7 +109,7 @@ defmodule Recode.Task.LocalsWithoutParens do locals_without_parens, autocorrect? ) do - if remove_parens?(fun, meta, args, locals_without_parens) do + if remove_parens?(fun, meta, args, locals_without_parens) do if autocorrect? do node = {fun, Keyword.delete(meta, :closing), args} {:cont, Zipper.replace(zipper, node), issues} @@ -123,7 +123,7 @@ defmodule Recode.Task.LocalsWithoutParens do end defp remove_parens?(fun, meta, args, locals_without_parens) do - Keyword.has_key?(meta, :closing) and not multiline?(meta) and + Keyword.has_key?(meta, :closing) and not multiline?(meta) and local_without_parens?(locals_without_parens, fun, args) end From 9325dca21c0587af92d4a504d923cda14a20166c Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Jan 2025 13:37:40 +0100 Subject: [PATCH 11/25] Update test --- .../task/locals_without_parens_test.exs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/recode/task/locals_without_parens_test.exs b/test/recode/task/locals_without_parens_test.exs index bf933f0..7fb2217 100644 --- a/test/recode/task/locals_without_parens_test.exs +++ b/test/recode/task/locals_without_parens_test.exs @@ -8,21 +8,22 @@ defmodule Recode.Task.LocalsWithoutParensTest do dot_formatter = DotFormatter.from_formatter_opts(locals_without_parens: [foo: 1, bar: 2, baz: :*]) - # code = """ - # [x] = foo(bar(y)) - # bar(y, x) - # baz(a) - # baz(a,b) - # baz(a,b,c) - # if(x == 1, do: true) - # """ - code = """ [x] = foo(bar(y)) + bar(y, x) + baz(a) + baz(a,b) + baz(a,b,c) + if(x == 1, do: true) """ expected = """ [x] = foo bar(y) + bar y, x + baz a + baz a, b + baz a, b, c + if x == 1, do: true """ code From 032566bfd6d83e3ca6de572ba40ee3e17cded532 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 10 Nov 2024 13:58:03 +0100 Subject: [PATCH 12/25] Changes for rewrite 1.0.0 (#96) --- examples/my_code/scripts/backup.bin | Bin 0 -> 1626 bytes examples/my_code/scripts/backup.exs | 38 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 examples/my_code/scripts/backup.bin create mode 100644 examples/my_code/scripts/backup.exs diff --git a/examples/my_code/scripts/backup.bin b/examples/my_code/scripts/backup.bin new file mode 100644 index 0000000000000000000000000000000000000000..3e096aa4a9e6224cbc726960f3dc78cbc481cb80 GIT binary patch literal 1626 zcmV-g2BrCfPyhfCO?cX+Slw>qG!!nkbFoOkBK~Ei7tAbaX4)+*JJMFHx>M~Us;){` zfT}8+Nj%fg#16Kz%`nU@yaAU;+;PQY@Fcthcbwzc`I*dCE2z}e$LI6$`L|uQwY7CS zh0og|XR~-d43apIJ{J)c9xWuWUi(|mky9G-hk(^8^kyH%@ zg)g~|)g^Iuc7j~-kPAUWsq0)N=jZmJ^1c&94Co=*BiKSKm1v2u2iYb>AC$+R7RPhUncz}+{ z3?_}LH*aX<*i9{og3P!fEM{4*H)4rCJT|+!@~Smb7poCM)Lx;WIAM z0NfzPWNBh^WT#wmez8K3f@q;g7!tC?*m(~znkN`wjRMX^@1ln}a0~VurVC%oq6JR< z713GBqdcMHcyS2xB;v5*i*hdFA#ofBCLZxHPbpJ@iaA5o$nz5a9BoAmuB!2mX_9bq z%|#M<$lBf}lOjkn2xL#|3htoAf!Wl%M0kH-IAgu8X9Pn`=!|GYsayphgtDlIzi7%w z4eLyr(;^F)1dWZgzph(bI%{HKrTnrS3}E>*&gfCjP8fB`*D;$X6lYdFqt}e{elf6$ z)c`Nz`81)(`-Ido>ghG@-|K2`ixg}0w~pqM@RE0erE|@yCWD0wh%p&c!0MfP9Wh6| zP=c#hl{72iU14G6O`UohJX!l@om0D~<9(x^OVHd^m1L!MRRpa2mtFtw4craxzdKkN z|8J6r?(%1LXS92X+k4=KK8Q0qmE@|5;G4||MwDhI>Hg{X{1Grqm2tOVI$g-VRDqyX z9>w5uoUk+@&@rN#B8I(lP)z_NfR47dto+f9Z|8uFHMhmmc55CLf{O;vc&83K^B!iIH>p4;S2rguzcM+?uPSZc zXzn-5ZC{vl%OYntOJ|&CP{rjI#b$FagDkV={?XauF>+tpZrR5Q+oyoSgOf)~+Sq7M zsmcYzW9&LuNb-EFp;cD~tjuUgQqChX24`4FXai0fAC=P^tw{4k#ik8K9V?EJi&o$P zmu6h3h6}B;s-|b$1ue0#ybOx7(LoFH(}BU)qy9jTr}O@xE?eE0biHq0zK+E+o9D{U zpahs~tie!%J}s~|K4+?*#8OpAB=i6_nXo+d;AR4+aeu~OW2hlP(|+NSy#aaffQ(Sy zpam~QR&!5IyqIBlRDbT_n#?Vme=sGHo)iTnR!v8L*~O;tq@OX}mT z#3=fEN6}M+;7#S_dgjoQ8rM;mm^&mFRULwp%OY)KPPx(3-8=b4RgxeL6>uu>eonKP*GVwjraSuCXT88i@A zHv*{yq>>Az_anR&_eV9hKTd)hp#yt8Q*jE9NO)aIbGFpiu&U^U}Rc!>PTc6F6f`8<#j!7&%Zy4va$>FP#X!dq7d-OatMpR^>$kU$?ftd?ZZK z|FT|HXVg}T_H^-##mYMj$~!p^aj8L~O`3G(f>I^*zXJK7lrpWa@tNLGynhq literal 0 HcmV?d00001 diff --git a/examples/my_code/scripts/backup.exs b/examples/my_code/scripts/backup.exs new file mode 100644 index 0000000..e4a1598 --- /dev/null +++ b/examples/my_code/scripts/backup.exs @@ -0,0 +1,38 @@ +defmodule Backup do + @inputs "{priv,config,lib,test}/**/*" + @backup "scripts/backup.bin" + + def run([]) do + @inputs + |> Path.wildcard(match_dot: true) + |> Enum.reject(&File.dir?/1) + |> Enum.into(%{}, fn path -> {path, File.read!(path)} end) + |> backup() + end + + def run(["restore"]) do + IO.puts("restoring form backup #{@backup}") + + Enum.each(["lib", "test", "config", "priv"], fn dir -> File.rm_rf!(dir) end) + + files = @backup |> File.read!() |> :erlang.binary_to_term() + + Enum.each(files, fn {path, data} -> + IO.puts("restoring #{path}") + path |> Path.dirname() |> File.mkdir_p!() + File.write!(path, data) + end) + end + + def run(argv) do + raise "Unknown args #{inspect(argv)}" + end + + defp backup(files) do + data = :erlang.term_to_binary(files, compressed: 9) + File.write!(@backup, data) + IO.puts("backup saved to #{@backup}") + end +end + +Backup.run(System.argv()) From 0ab693a6269f5c2607258e38d93774b4720cbdf7 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 10 Nov 2024 15:44:29 +0100 Subject: [PATCH 13/25] Add recode.issues manifest (#99) --- lib/recode/runner/impl.ex | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/lib/recode/runner/impl.ex b/lib/recode/runner/impl.ex index 62aebf5..99c4116 100644 --- a/lib/recode/runner/impl.ex +++ b/lib/recode/runner/impl.ex @@ -1,3 +1,55 @@ +defmodule Recode.Manifest do + @moduledoc false + + alias Rewrite.Source + + @manifest "recode.issues" + + def write(project, config) do + if config[:manifest] do + File.write(path(), content(project, config[:dry])) + else + :ok + end + end + + def read(config) do + if !config[:force] and config[:manifest] do + case File.read(path()) do + {:ok, content} -> {timestamp(), String.split(content, "\n")} + _error -> nil + end + else + nil + end + end + + def timestamp do + case File.stat(path(), time: :posix) do + {:ok, %{mtime: timestamp}} -> timestamp + {:error, _reason} -> 0 + end + end + + def path, do: Path.join(Mix.Project.manifest_path(), @manifest) + + defp content(project, dry) do + project + |> paths_with_issue(dry) + |> Enum.join("\n") + end + + defp paths_with_issue(project, dry) do + Enum.reduce(project, [], fn source, acc -> + if Source.has_issues?(source) or (dry and Source.updated?(source)) do + [source.path | acc] + else + acc + end + end) + end +end + defmodule Recode.Runner.Impl do @moduledoc false From be51cdbfd6a891bdd04545ba96699ff8150e4340 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 19 Nov 2024 07:37:07 +0100 Subject: [PATCH 14/25] Create issues for unformatted files in dry mode This feature is already part of recode, but was lost in the development of the new version. --- examples/minimal/lib/minimal.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/minimal/lib/minimal.ex b/examples/minimal/lib/minimal.ex index 4b22897..4fc3f60 100644 --- a/examples/minimal/lib/minimal.ex +++ b/examples/minimal/lib/minimal.ex @@ -14,6 +14,6 @@ defmodule Minimal do """ @spec hello :: :world def hello do - :world + :mars end end From 76d35fea3c3d3c5fe1fbf8fd0c7fecfa642596d8 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Jan 2025 09:22:38 +0100 Subject: [PATCH 15/25] Add task PipeChainStart --- .credo.exs | 2 +- lib/recode/ast.ex | 33 +- lib/recode/runner/impl.ex | 1 + lib/recode/task/pipe_chain_start.ex | 247 +++++++++++++ test/recode/task/pipe_chain_start_test.exs | 404 +++++++++++++++++++++ test/support/recode_case.ex | 17 +- 6 files changed, 699 insertions(+), 5 deletions(-) create mode 100644 lib/recode/task/pipe_chain_start.ex create mode 100644 test/recode/task/pipe_chain_start_test.exs diff --git a/.credo.exs b/.credo.exs index c4189b5..eb0641c 100644 --- a/.credo.exs +++ b/.credo.exs @@ -129,6 +129,7 @@ {Credo.Check.Refactor.NegatedConditionsInUnless, []}, {Credo.Check.Refactor.NegatedConditionsWithElse, []}, {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, + {Credo.Check.Refactor.PipeChainStart, []}, {Credo.Check.Refactor.RedundantWithClauseResult, []}, {Credo.Check.Refactor.RejectReject, []}, {Credo.Check.Refactor.UnlessWithElse, []}, @@ -195,7 +196,6 @@ {Credo.Check.Refactor.ModuleDependencies, []}, {Credo.Check.Refactor.NegatedIsNil, []}, {Credo.Check.Refactor.PassAsyncInTestCases, []}, - {Credo.Check.Refactor.PipeChainStart, []}, {Credo.Check.Refactor.RejectFilter, []}, {Credo.Check.Refactor.VariableRebinding, []}, {Credo.Check.Warning.LazyLogging, []}, diff --git a/lib/recode/ast.ex b/lib/recode/ast.ex index da93d89..0d873a7 100644 --- a/lib/recode/ast.ex +++ b/lib/recode/ast.ex @@ -452,6 +452,9 @@ defmodule Recode.AST do @doc """ Returns a `mfa`-tuple for the given `.`-call. + A `mfa`-tuple is a three-element tuple containing the module, function name, + and arity. + ## Examples iex> ast = quote do @@ -460,12 +463,38 @@ defmodule Recode.AST do ...> mfa(ast) {Foo.Bar, :baz, 1} """ - @spec mfa({{:., keyword(), list()}, metadata(), t()}) :: + @spec mfa({{:., metadata(), list()}, metadata(), t()}) :: {module(), atom(), non_neg_integer()} def mfa({{:., _meta1, [{:__aliases__, _meta2, aliases}, fun]}, _meta3, args}) do {Module.concat(aliases), fun, length(args)} end + @doc """ + Returns a `mf`-tuple for the given `.`-call or `.`-expression. + + A `mf`-tuple is a two-element tuple containing the module and function name. + + ## Examples + + iex> ast = quote do + ...> Foo.Bar.baz(x) + ...> end + ...> mf(ast) + {Foo.Bar, :baz} + iex> {ast, _, _} = ast + ...> mf(ast) + {Foo.Bar, :baz} + """ + @spec mfa({{:., metadata(), list()}, metadata(), t()} | {:., metadata(), list()}) :: + {module(), atom()} + def mf({{:., _meta1, [{:__aliases__, _meta2, aliases}, fun]}, _meta3, _args3}) do + {Module.concat(aliases), fun} + end + + def mf({:., _meta1, [{:__aliases__, _meta2, aliases}, fun]}) do + {Module.concat(aliases), fun} + end + @doc """ Puts the given value `newlines` under the key `nevlines` in `meta[:end_of_expression]`. @@ -658,7 +687,7 @@ defmodule Recode.AST do ...> def baz, do: baz ...> end ...> """) - ...> block = block(args) + ...> block = block(args) ...> Sourceror.to_string({:__block__,[], block}) """ def bar, do: bar diff --git a/lib/recode/runner/impl.ex b/lib/recode/runner/impl.ex index 99c4116..0e47fa6 100644 --- a/lib/recode/runner/impl.ex +++ b/lib/recode/runner/impl.ex @@ -102,6 +102,7 @@ defmodule Recode.Runner.Impl do @impl true def run(content, config, path \\ "source.ex") do + raise "ups" tasks = tasks(config) formatter_opts = Keyword.get(config, :formatter_opts, []) diff --git a/lib/recode/task/pipe_chain_start.ex b/lib/recode/task/pipe_chain_start.ex new file mode 100644 index 0000000..43bc56f --- /dev/null +++ b/lib/recode/task/pipe_chain_start.ex @@ -0,0 +1,247 @@ +defmodule Recode.Task.PipeChainStart do + @shortdoc "Checks if a pipe chain starts with a raw value." + + @moduledoc """ + # TODO + """ + + use Recode.Task, corrector: true, category: :readability + + alias Recode.AST + alias Rewrite.Source + alias Sourceror.Zipper + + @sigils [ + :sigil_C, + :sigil_c, + :sigil_D, + :sigil_N, + :sigil_r, + :sigil_S, + :sigil_s, + :sigil_T, + :sigil_U, + :sigil_W, + :sigil_w + ] + + @unary_ops [ + :!, + :"~~~", + :&, + :+, + :-, + :@, + :^, + :not + ] + + @binary_ops [ + :!=, + :!==, + :"//", + :"::", + :"<|>", + :"^^^", + :&&&, + :&&, + :**, + :*, + :+++, + :++, + :+, + :-, + :--, + :---, + :., + :.., + :"..//", + :/, + :<, + :<-, + :<<<, + :<<~, + :<=, + :<>, + :<~, + :<~>, + :=, + :==, + :===, + :=~, + :>, + :>=, + :>>>, + :\\, + :and, + :in, + :or, + :when, + :|, + :|>, + :||, + :|||, + :~>, + :~>> + ] + + @special_forms [ + :<<>>, + :__block__, + :case, + :cond, + :fn, + :for, + :if, + :unquote, + :unquote_splicing, + :with + ] + + @exclude_functions Enum.concat([@special_forms, @sigils, @unary_ops, @binary_ops]) + + @impl Recode.Task + def run(source, opts) do + source + |> Source.get(:quoted) + |> Zipper.zip() + |> Zipper.traverse([], fn zipper, issues -> + pipe_chain_start(zipper, issues, opts[:autocorrect], opts) + end) + |> update(source, opts) + end + + defp update({zipper, issues}, source, opts) do + update_source(source, opts, quoted: zipper, issues: issues) + end + + @impl Recode.Task + def init(config) do + config = + config + |> Keyword.put_new(:exclude_functions, []) + |> Keyword.put_new(:exclude_arguments, []) + + {:ok, config} + end + + # inside pipe + defp pipe_chain_start( + %Zipper{node: {:|>, _, [{:|>, _, _} | _]}} = zipper, + issues, + _autocorrect, + _opts + ) do + {zipper, issues} + end + + # pipe start + defp pipe_chain_start( + %Zipper{node: {:|>, meta, [lhs, rhs]}} = zipper, + issues, + autocorrect, + opts + ) do + case check(lhs, opts) do + :ok -> + {zipper, issues} + + {:error, correction} when autocorrect -> + {correct(zipper, correction, rhs), issues} + + {:error, _correction} when not autocorrect -> + {zipper, add_issue(issues, meta)} + end + end + + defp pipe_chain_start(zipper, issues, _autocorrect, _opts) do + {zipper, issues} + end + + defp check( + {{:., _meta1, [{:__aliases__, _meta2, _aliases2}, _fun]} = form, meta, [arg | rest]}, + opts + ) do + mf = AST.mf(form) + + with :error <- check(mf, meta, arg, opts) do + correction(arg, form, rest) + end + end + + defp check({{:., _meta1, [Access, :get]}, _meta, _args}, _opts) do + :ok + end + + defp check({{:., _meta1, [_arg1 | _rest1]} = form, meta, [arg | rest]}, opts) do + with :error <- check(:., meta, arg, opts) do + correction(arg, form, rest) + end + end + + defp check({form, meta, [arg | rest]}, opts) + when is_atom(form) and form not in @exclude_functions do + with :error <- check(form, meta, arg, opts) do + correction(arg, form, rest) + end + end + + defp check(_ast, _opts), do: :ok + + defp check(form, meta, arg, opts) do + with :error <- exclude_function(form, opts[:exclude_functions]), + :error <- exclude_argument(arg, opts[:exclude_arguments]) do + custom_sigil(form, meta[:delimiter]) + end + end + + defp correction(arg, form, rest) do + {:error, {:|>, [], [arg, {form, [], rest}]}} + end + + defp custom_sigil(_atom, nil), do: :error + + defp custom_sigil(atom, _delimeter) do + sigil? = atom |> to_string() |> String.starts_with?("sigil_") + + if sigil?, do: :ok, else: :error + end + + defp correct(zipper, lhs, rhs) do + Zipper.replace(zipper, {:|>, [], [lhs, rhs]}) + end + + defp add_issue(issues, meta) do + message = "Pipe chain should start with a raw value." + [new_issue(message, meta) | issues] + end + + defp exclude_function(_fun, []), do: :error + + defp exclude_function({module, fun}, exclude) do + if {module, fun} in exclude or {module, :*} in exclude, do: :ok, else: :error + end + + defp exclude_function(fun, exclude) do + if fun in exclude, do: :ok, else: :error + end + + defp exclude_argument({:__block__, _meta, [literal]}, _include) + when is_atom(literal) or is_number(literal) or is_binary(literal) or is_list(literal) do + :error + end + + defp exclude_argument({form, _meta, nil}, _include) when is_atom(form) do + :error + end + + defp exclude_argument({{:., _meta1, _args1}, _meta2, _args2}, _include) do + :error + end + + defp exclude_argument({form, _meta, _}, exclude) do + if form in exclude, do: :ok, else: :error + end + + defp exclude_argument(_arg, _include), do: :ok +end diff --git a/test/recode/task/pipe_chain_start_test.exs b/test/recode/task/pipe_chain_start_test.exs new file mode 100644 index 0000000..d7f9b1a --- /dev/null +++ b/test/recode/task/pipe_chain_start_test.exs @@ -0,0 +1,404 @@ +defmodule Recode.Task.PipeChainStartTest do + use RecodeCase + + alias Recode.Task.PipeChainStart + + describe "corrects" do + test "pipes with vars" do + code = """ + foo(x) |> bar() + foo(x, y) |> bar() + foo(x) |> bar() |> baz() + foo(x.y) |> bar() + foo(x.y, z) |> bar() + foo(u.v.w.x, y.z) |> bar() + foo(x[:y]) |> bar() + foo(x[:x][:y], z) |> bar() + foo(x["y"]) |> bar() + """ + + expected = """ + x |> foo() |> bar() + x |> foo(y) |> bar() + x |> foo() |> bar() |> baz() + x.y |> foo() |> bar() + x.y |> foo(z) |> bar() + u.v.w.x |> foo(y.z) |> bar() + x[:y] |> foo() |> bar() + x[:x][:y] |> foo(z) |> bar() + x["y"] |> foo() |> bar() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + + test "pipes with vars in dot calls" do + code = """ + Enum.reverse(x) |> bar() + Foo.foo(x, y) |> bar() + Foo.foo(x) |> bar() |> baz() + Foo.foo(x.y) |> bar() + Foo.foo(x.y, z) |> bar() + Foo.foo(u.v.w.x, y.z) |> bar() + Foo.foo(x[:y]) |> bar() + Foo.foo(x[:x][:y], z) |> bar() + Foo.foo(x["y"]) |> bar() + """ + + expected = """ + x |> Enum.reverse() |> bar() + x |> Foo.foo(y) |> bar() + x |> Foo.foo() |> bar() |> baz() + x.y |> Foo.foo() |> bar() + x.y |> Foo.foo(z) |> bar() + u.v.w.x |> Foo.foo(y.z) |> bar() + x[:y] |> Foo.foo() |> bar() + x[:x][:y] |> Foo.foo(z) |> bar() + x["y"] |> Foo.foo() |> bar() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + + test "pipes with vars in var function calls" do + code = """ + foo.(x) |> bar() + foo.bar.(x) |> bar() + foo.(x, y) |> bar() + foo.(x) |> bar() |> baz() + foo.(x) |> bar.() |> baz() + foo.(x.y) |> bar() + foo.(x.y, z) |> bar() + foo.(u.v.w.x, y.z) |> bar() + foo.(x[:y]) |> bar() + foo.(x[:x][:y], z) |> bar() + foo.(x["y"]) |> bar() + """ + + expected = """ + x |> foo.() |> bar() + x |> foo.bar.() |> bar() + x |> foo.(y) |> bar() + x |> foo.() |> bar() |> baz() + x |> foo.() |> bar.() |> baz() + x.y |> foo.() |> bar() + x.y |> foo.(z) |> bar() + u.v.w.x |> foo.(y.z) |> bar() + x[:y] |> foo.() |> bar() + x[:x][:y] |> foo.(z) |> bar() + x["y"] |> foo.() |> bar() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + + test "pipes with literals" do + code = """ + foo("x") |> bar() + foo("x", y) |> bar() + foo("x") |> bar() |> baz() + foo(:x) |> bar() + foo(42) |> bar() + foo(4.2) |> bar() + foo([2]) |> bar() + """ + + expected = """ + "x" |> foo() |> bar() + "x" |> foo(y) |> bar() + "x" |> foo() |> bar() |> baz() + :x |> foo() |> bar() + 42 |> foo() |> bar() + 4.2 |> foo() |> bar() + [2] |> foo() |> bar() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + + test "pipes with ranges" do + code = """ + foo(1..11) |> bar() + foo(1..11//3) |> bar() + """ + + expected = """ + 1..11 |> foo() |> bar() + 1..11//3 |> foo() |> bar() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + + test "pipes with functions and refs" do + code = """ + foo(fn x -> x * 2 end) |> bar() + foo(fn x -> x * 2 end, y, z) |> bar() + foo(&baz/5) |> bar() + foo(&baz/5,y ,z) |> bar() + """ + + expected = """ + fn x -> x * 2 end |> foo() |> bar() + fn x -> x * 2 end |> foo(y, z) |> bar() + (&baz/5) |> foo() |> bar() + (&baz/5) |> foo(y, z) |> bar() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + + test "in depth" do + code = """ + bar(foo(x)) |> baz() + bar(foo(a,b),c) |> baz(d) + foo(Enum.reverse(bar(x))) |> baz() + """ + + expected = """ + x |> foo() |> bar() |> baz() + a |> foo(b) |> bar(c) |> baz(d) + x |> bar() |> Enum.reverse() |> foo() |> baz() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + + test "crazy constructs" do + code = """ + bar(case x do + :u -> :x + :x -> :u + end) |> baz() + """ + + expected = """ + case x do + :u -> :x + :x -> :u + end + |> bar() + |> baz() + """ + + code + |> run_task(PipeChainStart, autocorrect: true) + |> assert_code(expected) + end + end + + describe "does not correct" do + test "pipes starting with a var" do + """ + def test(x) do + foo = x |> bar() + x |> baz(foo) |> bang() + end + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with accessing a var" do + """ + x.y |> foo() + x[:y] |> foo() + x["y"] |> foo() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with a literal" do + ~s''' + :foo |> foo() + 1 |> add(1) + 1 |> add(1) |> add(2) + "1" |> foo(1) + """ + foo + """ + |> foo() + ''' + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with a sigil" do + """ + ~s''' + foo + ''' |> foo() + ~r/.*/ |> foo() + ~CUSTOM"asdf" |> foo() + ~q"asdf" |> foo() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with an unary op" do + """ + +1 |> add(1) + -1 |> add(1) |> add(2) + not x |> foo() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with an binary op" do + """ + (1 + 1) |> foo(x) + x or y |> foo() + (x or y) |> foo() + x ~> foo() |> bar() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with a range" do + """ + 1..10 |> foo() + 1..10//2 |> foo() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with module attribute" do + """ + @foo |> bar() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with special forms" do + """ + quote do + unquote(x) |> foo() + end + + if x do + y + end + |> foo() + + case x do + :a -> "a" + _ -> "b" + end + |> foo() + + <> |> foo() + + cond do + x -> y + end + |> foo() + + with x <- y do + foo(x) + end |> bar() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with functions and refs" do + """ + fn x -> x + 1 end |> foo() + (&Enum.reverse/1) |> foo() + foo() |> bar() + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes starting with expected code" do + """ + defmodule RecodeTest do + defmacro a ~> b do + quote do + unquote(a) |> unquote(b) + end + end + + def test do + 1 + |> Kernel.*(2) + ~> Kernel.*(2) + |> Kernel.*(2) + end + end + """ + |> run_task(PipeChainStart, autocorrect: true) + |> refute_update() + end + + test "pipes with functions and refs if excluded" do + """ + foo(fn x -> x * 2 end) |> bar() + foo(&baz/5) |> bar() + """ + |> run_task(PipeChainStart, autocorrect: true, exclude_arguments: [:fn, :&]) + |> refute_update() + end + + test "crazy constructs if excluded" do + """ + bar(case x do + :u -> :x + :x -> :u + end) |> baz() + """ + |> run_task(PipeChainStart, autocorrect: true, exclude_arguments: [:case]) + |> refute_update() + end + + test "pipes if start function is excluded" do + """ + foo(x) |> bar() + Foo.foo(x) |> bar() + Bar.foo(x) |> foo() + Bar.bar(x) |> foo() + """ + |> run_task(PipeChainStart, autocorrect: true, exclude_functions: [:foo, {Foo, :foo}, {Bar, :*}]) + |> refute_update() + end + end + + describe "reports an issue for" do + test "a single pipe" do + """ + def test(x) do + foo(x) |> bar() + end + """ + |> run_task(PipeChainStart, autocorrect: false) + |> assert_issue_with( + message: "Pipe chain should start with a raw value.", + reporter: Recode.Task.PipeChainStart, + line: 2, + column: 10, + meta: nil + ) + end + end +end diff --git a/test/support/recode_case.ex b/test/support/recode_case.ex index 9919127..1da7d25 100644 --- a/test/support/recode_case.ex +++ b/test/support/recode_case.ex @@ -76,13 +76,26 @@ defmodule RecodeCase do defmacro refute_update(source) do quote bind_quoted: [source: source] do - refute Source.updated?(source) + message = + Escape.format([ + :red, + "Expected no updates, got #{Source.version(source) - 1} update(s):\n\n", + :reset, + Source.diff(source) + ]) + + refute Source.updated?(source), IO.iodata_to_binary(message) end end defmacro assert_code(source, expected) do quote bind_quoted: [source: source, expected: expected] do - assert Source.get(source, :content) == expected + content = Source.get(source, :content) + + diff = + Escape.format([:red, "Assertion code == expected\n\n", TextDiff.format(content, expected)]) + + assert content == expected, IO.iodata_to_binary(diff) end end From 313cfe81efe424a6832fe2769ee9e75b58b7ba41 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Jan 2025 14:05:28 +0100 Subject: [PATCH 16/25] Update .credo.exs --- .credo.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.credo.exs b/.credo.exs index eb0641c..c4189b5 100644 --- a/.credo.exs +++ b/.credo.exs @@ -129,7 +129,6 @@ {Credo.Check.Refactor.NegatedConditionsInUnless, []}, {Credo.Check.Refactor.NegatedConditionsWithElse, []}, {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, - {Credo.Check.Refactor.PipeChainStart, []}, {Credo.Check.Refactor.RedundantWithClauseResult, []}, {Credo.Check.Refactor.RejectReject, []}, {Credo.Check.Refactor.UnlessWithElse, []}, @@ -196,6 +195,7 @@ {Credo.Check.Refactor.ModuleDependencies, []}, {Credo.Check.Refactor.NegatedIsNil, []}, {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, {Credo.Check.Refactor.RejectFilter, []}, {Credo.Check.Refactor.VariableRebinding, []}, {Credo.Check.Warning.LazyLogging, []}, From bba5e2856092c8f0e1dc2ff4e8d89c72ca8b47f2 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 21 Jan 2025 06:46:56 +0100 Subject: [PATCH 17/25] Clean up --- examples/minimal/lib/minimal.ex | 2 +- lib/recode/ast.ex | 2 +- lib/recode/config.ex | 1 + lib/recode/runner/impl.ex | 53 --------------------------------- 4 files changed, 3 insertions(+), 55 deletions(-) diff --git a/examples/minimal/lib/minimal.ex b/examples/minimal/lib/minimal.ex index 4fc3f60..4b22897 100644 --- a/examples/minimal/lib/minimal.ex +++ b/examples/minimal/lib/minimal.ex @@ -14,6 +14,6 @@ defmodule Minimal do """ @spec hello :: :world def hello do - :mars + :world end end diff --git a/lib/recode/ast.ex b/lib/recode/ast.ex index 0d873a7..efdc801 100644 --- a/lib/recode/ast.ex +++ b/lib/recode/ast.ex @@ -485,7 +485,7 @@ defmodule Recode.AST do ...> mf(ast) {Foo.Bar, :baz} """ - @spec mfa({{:., metadata(), list()}, metadata(), t()} | {:., metadata(), list()}) :: + @spec mf({{:., metadata(), list()}, metadata(), t()} | {:., metadata(), list()}) :: {module(), atom()} def mf({{:., _meta1, [{:__aliases__, _meta2, aliases}, fun]}, _meta3, _args3}) do {Module.concat(aliases), fun} diff --git a/lib/recode/config.ex b/lib/recode/config.ex index 22ea770..ca3d414 100644 --- a/lib/recode/config.ex +++ b/lib/recode/config.ex @@ -48,6 +48,7 @@ defmodule Recode.Config do {Recode.Task.LocalsWithoutParens, []}, {Recode.Task.Moduledoc, [exclude: ["test/**/*.{ex,exs}", "mix.exs"]]}, {Recode.Task.Nesting, []}, + {Recode.Task.PipeChainStart, [active: false]}, {Recode.Task.PipeFunOne, []}, {Recode.Task.SinglePipe, []}, {Recode.Task.Specs, [exclude: ["test/**/*.{ex,exs}", "mix.exs"], config: [only: :visible]]}, diff --git a/lib/recode/runner/impl.ex b/lib/recode/runner/impl.ex index 0e47fa6..62aebf5 100644 --- a/lib/recode/runner/impl.ex +++ b/lib/recode/runner/impl.ex @@ -1,55 +1,3 @@ -defmodule Recode.Manifest do - @moduledoc false - - alias Rewrite.Source - - @manifest "recode.issues" - - def write(project, config) do - if config[:manifest] do - File.write(path(), content(project, config[:dry])) - else - :ok - end - end - - def read(config) do - if !config[:force] and config[:manifest] do - case File.read(path()) do - {:ok, content} -> {timestamp(), String.split(content, "\n")} - _error -> nil - end - else - nil - end - end - - def timestamp do - case File.stat(path(), time: :posix) do - {:ok, %{mtime: timestamp}} -> timestamp - {:error, _reason} -> 0 - end - end - - def path, do: Path.join(Mix.Project.manifest_path(), @manifest) - - defp content(project, dry) do - project - |> paths_with_issue(dry) - |> Enum.join("\n") - end - - defp paths_with_issue(project, dry) do - Enum.reduce(project, [], fn source, acc -> - if Source.has_issues?(source) or (dry and Source.updated?(source)) do - [source.path | acc] - else - acc - end - end) - end -end - defmodule Recode.Runner.Impl do @moduledoc false @@ -102,7 +50,6 @@ defmodule Recode.Runner.Impl do @impl true def run(content, config, path \\ "source.ex") do - raise "ups" tasks = tasks(config) formatter_opts = Keyword.get(config, :formatter_opts, []) From 58e2e4c7eda1186b4e495671876e21b79dc8daed Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 21 Jan 2025 07:09:43 +0100 Subject: [PATCH 18/25] Run mix format --- lib/recode/task/pipe_chain_start.ex | 2 +- test/recode/task/pipe_chain_start_test.exs | 5 ++++- test/support/recode_case.ex | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/recode/task/pipe_chain_start.ex b/lib/recode/task/pipe_chain_start.ex index 43bc56f..04bb1e5 100644 --- a/lib/recode/task/pipe_chain_start.ex +++ b/lib/recode/task/pipe_chain_start.ex @@ -55,7 +55,7 @@ defmodule Recode.Task.PipeChainStart do :---, :., :.., - :"..//", + :..//, :/, :<, :<-, diff --git a/test/recode/task/pipe_chain_start_test.exs b/test/recode/task/pipe_chain_start_test.exs index d7f9b1a..107bb01 100644 --- a/test/recode/task/pipe_chain_start_test.exs +++ b/test/recode/task/pipe_chain_start_test.exs @@ -379,7 +379,10 @@ defmodule Recode.Task.PipeChainStartTest do Bar.foo(x) |> foo() Bar.bar(x) |> foo() """ - |> run_task(PipeChainStart, autocorrect: true, exclude_functions: [:foo, {Foo, :foo}, {Bar, :*}]) + |> run_task(PipeChainStart, + autocorrect: true, + exclude_functions: [:foo, {Foo, :foo}, {Bar, :*}] + ) |> refute_update() end end diff --git a/test/support/recode_case.ex b/test/support/recode_case.ex index 1da7d25..46e5f31 100644 --- a/test/support/recode_case.ex +++ b/test/support/recode_case.ex @@ -93,7 +93,11 @@ defmodule RecodeCase do content = Source.get(source, :content) diff = - Escape.format([:red, "Assertion code == expected\n\n", TextDiff.format(content, expected)]) + Escape.format([ + :red, + "Assertion code == expected\n\n", + TextDiff.format(content, expected) + ]) assert content == expected, IO.iodata_to_binary(diff) end From 2393c55e3119baf391750d4ab53985305cb584b7 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 21 Jan 2025 07:11:34 +0100 Subject: [PATCH 19/25] Set fail-fast to false --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cb5e43..e1986f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ jobs: name: Test on Ubuntu (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }}) runs-on: ubuntu-24.04 strategy: + fail-fast: false matrix: include: - elixir: '1.19.0' From 938ad79cbc82a4549dcb1bed982444ef69435684 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Tue, 21 Jan 2025 07:16:10 +0100 Subject: [PATCH 20/25] Fix test --- test/recode/task/pipe_chain_start_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/recode/task/pipe_chain_start_test.exs b/test/recode/task/pipe_chain_start_test.exs index 107bb01..3bcfac6 100644 --- a/test/recode/task/pipe_chain_start_test.exs +++ b/test/recode/task/pipe_chain_start_test.exs @@ -244,7 +244,6 @@ defmodule Recode.Task.PipeChainStartTest do foo ''' |> foo() ~r/.*/ |> foo() - ~CUSTOM"asdf" |> foo() ~q"asdf" |> foo() """ |> run_task(PipeChainStart, autocorrect: true) From df2edeecfc3c376931a063723351a5e7e576fd54 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Fri, 10 Oct 2025 20:22:40 +0200 Subject: [PATCH 21/25] rm scripts --- examples/my_code/scripts/backup.bin | Bin 1626 -> 0 bytes examples/my_code/scripts/backup.exs | 38 ---------------------------- 2 files changed, 38 deletions(-) delete mode 100644 examples/my_code/scripts/backup.bin delete mode 100644 examples/my_code/scripts/backup.exs diff --git a/examples/my_code/scripts/backup.bin b/examples/my_code/scripts/backup.bin deleted file mode 100644 index 3e096aa4a9e6224cbc726960f3dc78cbc481cb80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1626 zcmV-g2BrCfPyhfCO?cX+Slw>qG!!nkbFoOkBK~Ei7tAbaX4)+*JJMFHx>M~Us;){` zfT}8+Nj%fg#16Kz%`nU@yaAU;+;PQY@Fcthcbwzc`I*dCE2z}e$LI6$`L|uQwY7CS zh0og|XR~-d43apIJ{J)c9xWuWUi(|mky9G-hk(^8^kyH%@ zg)g~|)g^Iuc7j~-kPAUWsq0)N=jZmJ^1c&94Co=*BiKSKm1v2u2iYb>AC$+R7RPhUncz}+{ z3?_}LH*aX<*i9{og3P!fEM{4*H)4rCJT|+!@~Smb7poCM)Lx;WIAM z0NfzPWNBh^WT#wmez8K3f@q;g7!tC?*m(~znkN`wjRMX^@1ln}a0~VurVC%oq6JR< z713GBqdcMHcyS2xB;v5*i*hdFA#ofBCLZxHPbpJ@iaA5o$nz5a9BoAmuB!2mX_9bq z%|#M<$lBf}lOjkn2xL#|3htoAf!Wl%M0kH-IAgu8X9Pn`=!|GYsayphgtDlIzi7%w z4eLyr(;^F)1dWZgzph(bI%{HKrTnrS3}E>*&gfCjP8fB`*D;$X6lYdFqt}e{elf6$ z)c`Nz`81)(`-Ido>ghG@-|K2`ixg}0w~pqM@RE0erE|@yCWD0wh%p&c!0MfP9Wh6| zP=c#hl{72iU14G6O`UohJX!l@om0D~<9(x^OVHd^m1L!MRRpa2mtFtw4craxzdKkN z|8J6r?(%1LXS92X+k4=KK8Q0qmE@|5;G4||MwDhI>Hg{X{1Grqm2tOVI$g-VRDqyX z9>w5uoUk+@&@rN#B8I(lP)z_NfR47dto+f9Z|8uFHMhmmc55CLf{O;vc&83K^B!iIH>p4;S2rguzcM+?uPSZc zXzn-5ZC{vl%OYntOJ|&CP{rjI#b$FagDkV={?XauF>+tpZrR5Q+oyoSgOf)~+Sq7M zsmcYzW9&LuNb-EFp;cD~tjuUgQqChX24`4FXai0fAC=P^tw{4k#ik8K9V?EJi&o$P zmu6h3h6}B;s-|b$1ue0#ybOx7(LoFH(}BU)qy9jTr}O@xE?eE0biHq0zK+E+o9D{U zpahs~tie!%J}s~|K4+?*#8OpAB=i6_nXo+d;AR4+aeu~OW2hlP(|+NSy#aaffQ(Sy zpam~QR&!5IyqIBlRDbT_n#?Vme=sGHo)iTnR!v8L*~O;tq@OX}mT z#3=fEN6}M+;7#S_dgjoQ8rM;mm^&mFRULwp%OY)KPPx(3-8=b4RgxeL6>uu>eonKP*GVwjraSuCXT88i@A zHv*{yq>>Az_anR&_eV9hKTd)hp#yt8Q*jE9NO)aIbGFpiu&U^U}Rc!>PTc6F6f`8<#j!7&%Zy4va$>FP#X!dq7d-OatMpR^>$kU$?ftd?ZZK z|FT|HXVg}T_H^-##mYMj$~!p^aj8L~O`3G(f>I^*zXJK7lrpWa@tNLGynhq diff --git a/examples/my_code/scripts/backup.exs b/examples/my_code/scripts/backup.exs deleted file mode 100644 index e4a1598..0000000 --- a/examples/my_code/scripts/backup.exs +++ /dev/null @@ -1,38 +0,0 @@ -defmodule Backup do - @inputs "{priv,config,lib,test}/**/*" - @backup "scripts/backup.bin" - - def run([]) do - @inputs - |> Path.wildcard(match_dot: true) - |> Enum.reject(&File.dir?/1) - |> Enum.into(%{}, fn path -> {path, File.read!(path)} end) - |> backup() - end - - def run(["restore"]) do - IO.puts("restoring form backup #{@backup}") - - Enum.each(["lib", "test", "config", "priv"], fn dir -> File.rm_rf!(dir) end) - - files = @backup |> File.read!() |> :erlang.binary_to_term() - - Enum.each(files, fn {path, data} -> - IO.puts("restoring #{path}") - path |> Path.dirname() |> File.mkdir_p!() - File.write!(path, data) - end) - end - - def run(argv) do - raise "Unknown args #{inspect(argv)}" - end - - defp backup(files) do - data = :erlang.term_to_binary(files, compressed: 9) - File.write!(@backup, data) - IO.puts("backup saved to #{@backup}") - end -end - -Backup.run(System.argv()) From 8fb1db83f029a53236dffb60560415752c4783f6 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Fri, 10 Oct 2025 20:24:45 +0200 Subject: [PATCH 22/25] update ci --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1986f3..1cb5e43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,6 @@ jobs: name: Test on Ubuntu (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }}) runs-on: ubuntu-24.04 strategy: - fail-fast: false matrix: include: - elixir: '1.19.0' From 5474d135564fe3cf909b22732035cb04f0b51700 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Fri, 10 Oct 2025 20:37:00 +0200 Subject: [PATCH 23/25] Fix filter_count --- lib/recode/task/filter_count.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/recode/task/filter_count.ex b/lib/recode/task/filter_count.ex index efbc562..2933bbe 100644 --- a/lib/recode/task/filter_count.ex +++ b/lib/recode/task/filter_count.ex @@ -36,6 +36,10 @@ defmodule Recode.Task.FilterCount do |> update(source, opts) end + defp update({zipper, _issues}, source, true) do + Source.update(source, :quoted, Zipper.root(zipper), by: __MODULE__) + end + defp update({zipper, issues}, source, opts) do update_source(source, opts, quoted: zipper, issues: issues) end From 077192342f06c32120761c612fe2b705e38221da Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Oct 2025 10:07:16 +0200 Subject: [PATCH 24/25] Add docs --- lib/recode/task/pipe_chain_start.ex | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/recode/task/pipe_chain_start.ex b/lib/recode/task/pipe_chain_start.ex index 04bb1e5..d89e1c0 100644 --- a/lib/recode/task/pipe_chain_start.ex +++ b/lib/recode/task/pipe_chain_start.ex @@ -2,7 +2,21 @@ defmodule Recode.Task.PipeChainStart do @shortdoc "Checks if a pipe chain starts with a raw value." @moduledoc """ - # TODO + Pipes should start with a raw value to improve readability. + + # preferred + user + |> User.changeset(params) + |> Repo.insert() + + # not preferred + User.changeset(user, params) + |> Repo.insert() + + Starting a pipe chain with a function call instead of a raw value can make + the code harder to follow, as the data flow is less clear. + + This task rewrites the code when `mix recode` runs with `autocorrect: true`. """ use Recode.Task, corrector: true, category: :readability From 3bf5df085798e4a544e8566e344e579750846166 Mon Sep 17 00:00:00 2001 From: Marcus Kruse Date: Sun, 19 Oct 2025 12:20:55 +0200 Subject: [PATCH 25/25] Fix typo --- lib/recode/task/pipe_chain_start.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/recode/task/pipe_chain_start.ex b/lib/recode/task/pipe_chain_start.ex index d89e1c0..99b27c2 100644 --- a/lib/recode/task/pipe_chain_start.ex +++ b/lib/recode/task/pipe_chain_start.ex @@ -215,7 +215,7 @@ defmodule Recode.Task.PipeChainStart do defp custom_sigil(_atom, nil), do: :error - defp custom_sigil(atom, _delimeter) do + defp custom_sigil(atom, _delimiter) do sigil? = atom |> to_string() |> String.starts_with?("sigil_") if sigil?, do: :ok, else: :error