From 75eea1e3c5e80e6ac294cab36bfb062fd95e03a9 Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Fri, 22 Nov 2024 13:10:42 -0800 Subject: [PATCH 1/5] spike on sorting comment directive. closes #167 --- lib/style/comment_directives.ex | 44 ++++++++++++++++++++++++++ lib/styler.ex | 3 +- test/style/comment_directives_test.exs | 41 ++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 lib/style/comment_directives.ex create mode 100644 test/style/comment_directives_test.exs diff --git a/lib/style/comment_directives.ex b/lib/style/comment_directives.ex new file mode 100644 index 00000000..12fad4aa --- /dev/null +++ b/lib/style/comment_directives.ex @@ -0,0 +1,44 @@ +# Copyright 2024 Adobe. All rights reserved. +# This file is licensed to you under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. You may obtain a copy +# of the License at http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +# OF ANY KIND, either express or implied. See the License for the specific language +# governing permissions and limitations under the License. + +defmodule Styler.Style.CommentDirectives do + @moduledoc "TODO" + + @behaviour Styler.Style + + alias Styler.Zipper + + def run(zipper, ctx) do + zipper = + ctx.comments + |> Enum.filter(&(&1.text == "# styler:sort")) + |> Enum.map(& &1.line) + |> Enum.reduce(zipper, fn line, zipper -> + found = + Zipper.find(zipper, fn + {_, meta, _} -> meta[:line] >= line + _ -> false + end) + + case found do + nil -> + zipper + + {{:__block__, meta, [list]}, _} when is_list(list) -> + Zipper.replace(found, {:__block__, meta, [Enum.sort_by(list, fn {f, _, a} -> {f, a} end)]}) + + _ -> + found + end + end) + + {:skip, zipper, ctx} + end +end diff --git a/lib/styler.ex b/lib/styler.ex index 8179c415..50e0d20b 100644 --- a/lib/styler.ex +++ b/lib/styler.ex @@ -25,7 +25,8 @@ defmodule Styler do Styler.Style.Defs, Styler.Style.Blocks, Styler.Style.Deprecations, - Styler.Style.Configs + Styler.Style.Configs, + Styler.Style.CommentDirectives ] @doc false diff --git a/test/style/comment_directives_test.exs b/test/style/comment_directives_test.exs new file mode 100644 index 00000000..32db529d --- /dev/null +++ b/test/style/comment_directives_test.exs @@ -0,0 +1,41 @@ +# Copyright 2024 Adobe. All rights reserved. +# This file is licensed to you under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. You may obtain a copy +# of the License at http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +# OF ANY KIND, either express or implied. See the License for the specific language +# governing permissions and limitations under the License. + +defmodule Styler.Style.CommentDirectivesTest do + @moduledoc false + use Styler.StyleCase, async: true + + describe "sort" do + test "we dont just sort by accident" do + assert_style "[:c, :b, :a]" + end + + test "sorts lists" do + assert_style( + """ + # styler:sort + [ + :c, + :b, + :a + ] + """, + """ + # styler:sort + [ + :a, + :b, + :c + ] + """ + ) + end + end +end From 01731cef90d537caf23b16a93211549c091fdbda Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Fri, 22 Nov 2024 13:41:05 -0800 Subject: [PATCH 2/5] word lists --- lib/style/comment_directives.ex | 24 ++++++++++++++++++-- test/style/comment_directives_test.exs | 31 +++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/style/comment_directives.ex b/lib/style/comment_directives.ex index 12fad4aa..e412b22a 100644 --- a/lib/style/comment_directives.ex +++ b/lib/style/comment_directives.ex @@ -32,9 +32,29 @@ defmodule Styler.Style.CommentDirectives do zipper {{:__block__, meta, [list]}, _} when is_list(list) -> - Zipper.replace(found, {:__block__, meta, [Enum.sort_by(list, fn {f, _, a} -> {f, a} end)]}) + list = Enum.sort_by(list, fn {f, _, a} -> {f, a} end) + Zipper.replace(found, {:__block__, meta, [list]}) - _ -> + {{:sigil_w, sm, [{:<<>>, bm, [string]}, modifiers]} = node, _} -> + dbg(node) + # ew. gotta be a better way. + # this keeps indentation for the sigil via joiner, while prepend and append are the bookending whitespace + {prepend, joiner, append} = + case Regex.run(~r|^\s+|, string) do + # oneliner like `~w|c a b|` + nil -> {"", " ", ""} + # multline like + # `"\n a\n list\n long\n of\n static\n values\n"` + # ^^^^ `prepend` ^^^^ `joiner` ^^ `append` + # note that joiner and append are the same in a multline (unsure if this is always true) + [joiner] -> {joiner, joiner, if(String.contains?(joiner, "\n"), do: "\n", else: "")} + end + + string = string |> String.split() |> Enum.sort() |> Enum.join(joiner) + Zipper.replace(found, {:sigil_w, sm, [{:<<>>, bm, [prepend, string, append]}, modifiers]}) + + x -> + dbg(Zipper.node(x)) found end end) diff --git a/test/style/comment_directives_test.exs b/test/style/comment_directives_test.exs index 32db529d..9a104ad8 100644 --- a/test/style/comment_directives_test.exs +++ b/test/style/comment_directives_test.exs @@ -17,7 +17,7 @@ defmodule Styler.Style.CommentDirectivesTest do assert_style "[:c, :b, :a]" end - test "sorts lists" do + test "sorts lists of atoms" do assert_style( """ # styler:sort @@ -37,5 +37,34 @@ defmodule Styler.Style.CommentDirectivesTest do """ ) end + + test "sorts sigils" do + assert_style("# styler:sort\n~w|c a b|", "# styler:sort\n~w|a b c|") + + assert_style( + """ + # styler:sort + ~w( + a + long + list + of + static + values + ) + """, + """ + # styler:sort + ~w( + a + list + long + of + static + values + ) + """ + ) + end end end From 596306be57be6ff6afb67a5059e4b454442f8c33 Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Fri, 22 Nov 2024 13:58:52 -0800 Subject: [PATCH 3/5] assignments --- lib/style/comment_directives.ex | 64 ++++++++++++++------------ test/style/comment_directives_test.exs | 60 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 30 deletions(-) diff --git a/lib/style/comment_directives.ex b/lib/style/comment_directives.ex index e412b22a..44b0b34c 100644 --- a/lib/style/comment_directives.ex +++ b/lib/style/comment_directives.ex @@ -23,42 +23,46 @@ defmodule Styler.Style.CommentDirectives do |> Enum.reduce(zipper, fn line, zipper -> found = Zipper.find(zipper, fn - {_, meta, _} -> meta[:line] >= line + {_, meta, _} -> Keyword.get(meta, :line, -1) >= line _ -> false end) - case found do - nil -> - zipper - - {{:__block__, meta, [list]}, _} when is_list(list) -> - list = Enum.sort_by(list, fn {f, _, a} -> {f, a} end) - Zipper.replace(found, {:__block__, meta, [list]}) - - {{:sigil_w, sm, [{:<<>>, bm, [string]}, modifiers]} = node, _} -> - dbg(node) - # ew. gotta be a better way. - # this keeps indentation for the sigil via joiner, while prepend and append are the bookending whitespace - {prepend, joiner, append} = - case Regex.run(~r|^\s+|, string) do - # oneliner like `~w|c a b|` - nil -> {"", " ", ""} - # multline like - # `"\n a\n list\n long\n of\n static\n values\n"` - # ^^^^ `prepend` ^^^^ `joiner` ^^ `append` - # note that joiner and append are the same in a multline (unsure if this is always true) - [joiner] -> {joiner, joiner, if(String.contains?(joiner, "\n"), do: "\n", else: "")} - end - - string = string |> String.split() |> Enum.sort() |> Enum.join(joiner) - Zipper.replace(found, {:sigil_w, sm, [{:<<>>, bm, [prepend, string, append]}, modifiers]}) - - x -> - dbg(Zipper.node(x)) - found + if found do + Zipper.update(found, &sort/1) + else + zipper end end) {:skip, zipper, ctx} end + + defp sort({:__block__, meta, [list]}) when is_list(list) do + list = Enum.sort_by(list, fn {f, _, a} -> {f, a} end) + {:__block__, meta, [list]} + end + + defp sort({:sigil_w, sm, [{:<<>>, bm, [string]}, modifiers]}) do + # ew. gotta be a better way. + # this keeps indentation for the sigil via joiner, while prepend and append are the bookending whitespace + {prepend, joiner, append} = + case Regex.run(~r|^\s+|, string) do + # oneliner like `~w|c a b|` + nil -> {"", " ", ""} + # multline like + # `"\n a\n list\n long\n of\n static\n values\n"` + # ^^^^ `prepend` ^^^^ `joiner` ^^ `append` + # note that joiner and prepend are the same in a multiline (unsure if this is always true) + #@TODO: get all 3 in one pass of a regex. probably have to turn off greedy or something... + [joiner] -> {joiner, joiner, ~r|\s+$| |> Regex.run(string) |> hd} + end + + string = string |> String.split() |> Enum.sort() |> Enum.join(joiner) + {:sigil_w, sm, [{:<<>>, bm, [prepend, string, append]}, modifiers]} + end + + defp sort({:=, m, [lhs, rhs]}), do: {:=, m, [lhs, sort(rhs)]} + defp sort({:@, m, [{a, am, [assignment]}]}), do: {:@, m, [{a, am, [sort(assignment)]}]} + + defp sort(x), do: dbg(x) end diff --git a/test/style/comment_directives_test.exs b/test/style/comment_directives_test.exs index 9a104ad8..071a1216 100644 --- a/test/style/comment_directives_test.exs +++ b/test/style/comment_directives_test.exs @@ -66,5 +66,65 @@ defmodule Styler.Style.CommentDirectivesTest do """ ) end + + test "assignments" do + assert_style( + """ + # styler:sort + my_var = + ~w( + a + long + list + of + static + values + ) + """, + """ + # styler:sort + my_var = + ~w( + a + list + long + of + static + values + ) + """ + ) + + assert_style( + """ + defmodule M do + @moduledoc false + # styler:sort + @attr ~w( + a + long + list + of + static + values + ) + end + """, + """ + defmodule M do + @moduledoc false + # styler:sort + @attr ~w( + a + list + long + of + static + values + ) + end + """ + ) + end end end From 12cc3f7960cc512f03fc19e07ff9a8834c8cf58b Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Fri, 22 Nov 2024 16:12:30 -0800 Subject: [PATCH 4/5] done? --- lib/style/comment_directives.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/style/comment_directives.ex b/lib/style/comment_directives.ex index 44b0b34c..b0ba0135 100644 --- a/lib/style/comment_directives.ex +++ b/lib/style/comment_directives.ex @@ -53,8 +53,8 @@ defmodule Styler.Style.CommentDirectives do # `"\n a\n list\n long\n of\n static\n values\n"` # ^^^^ `prepend` ^^^^ `joiner` ^^ `append` # note that joiner and prepend are the same in a multiline (unsure if this is always true) - #@TODO: get all 3 in one pass of a regex. probably have to turn off greedy or something... - [joiner] -> {joiner, joiner, ~r|\s+$| |> Regex.run(string) |> hd} + # @TODO: get all 3 in one pass of a regex. probably have to turn off greedy or something... + [joiner] -> {joiner, joiner, ~r|\s+$| |> Regex.run(string) |> hd()} end string = string |> String.split() |> Enum.sort() |> Enum.join(joiner) @@ -63,6 +63,5 @@ defmodule Styler.Style.CommentDirectives do defp sort({:=, m, [lhs, rhs]}), do: {:=, m, [lhs, sort(rhs)]} defp sort({:@, m, [{a, am, [assignment]}]}), do: {:@, m, [{a, am, [sort(assignment)]}]} - - defp sort(x), do: dbg(x) + defp sort(x), do: x end From 987e12a9c451acbae72d826861acf832084e3b7c Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Fri, 22 Nov 2024 18:34:28 -0700 Subject: [PATCH 5/5] ship it squirrel goes here --- CHANGELOG.md | 66 ++++++++++++++++++++++++++ lib/style/comment_directives.ex | 2 +- test/style/comment_directives_test.exs | 27 +++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3cb4bc1..e40e0c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,72 @@ they can and will change without that change being reflected in Styler's semantic version. ## main +### Improvements + +#### `# styler:sort` Styler's first comment directive + +Styler will now keep a user-designated list or wordlist (`~w` sigil) sorted as part of formatting via the use of comments. + +The intention is to remove comments to humans, like `# Please keep this list sorted!`, in favor of comments to robots: `# styler:sort`. Personally speaking, Styler is much better at alphabetical-order than I ever will be. + +To use the new directive, put it on the line before a list or wordlist. + +This example: + +```elixir +# styler:sort +[:c, :a, :b] + +# styler:sort +~w(a list of words) + +# styler:sort +@country_codes ~w( + en_US + po_PO + fr_CA + ja_JP +) + +# styler:sort +a_var = + [ + Modules, + In, + A, + List + ] +``` + +Would yield: + +```elixir +# styler:sort +[:a, :b, :c] + +# styler:sort +~w(a list of words) + +# styler:sort +@country_codes ~w( + en_US + fr_CA + ja_JP + po_PO +) + +# styler:sort +a_var = + [ + A, + In, + List, + Modules + ] +``` + +Sorting is done according to erlang term ordering, so lists with elements of multiple types will work just fine. + ## 1.2.1 ### Fixes diff --git a/lib/style/comment_directives.ex b/lib/style/comment_directives.ex index b0ba0135..d41e0881 100644 --- a/lib/style/comment_directives.ex +++ b/lib/style/comment_directives.ex @@ -34,7 +34,7 @@ defmodule Styler.Style.CommentDirectives do end end) - {:skip, zipper, ctx} + {:halt, zipper, ctx} end defp sort({:__block__, meta, [list]}) when is_list(list) do diff --git a/test/style/comment_directives_test.exs b/test/style/comment_directives_test.exs index 071a1216..16c8621a 100644 --- a/test/style/comment_directives_test.exs +++ b/test/style/comment_directives_test.exs @@ -126,5 +126,32 @@ defmodule Styler.Style.CommentDirectivesTest do """ ) end + + test "doesnt affect downstream nodes" do + assert_style( + """ + # styler:sort + [:c, :a, :b] + + @country_codes ~w( + po_PO + en_US + fr_CA + ja_JP + ) + """, + """ + # styler:sort + [:a, :b, :c] + + @country_codes ~w( + po_PO + en_US + fr_CA + ja_JP + ) + """ + ) + end end end