Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions lib/style/comment_directives.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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, _} -> Keyword.get(meta, :line, -1) >= line
_ -> false
end)

if found do
Zipper.update(found, &sort/1)
else
zipper
end
end)

{:halt, 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: x
end
3 changes: 2 additions & 1 deletion lib/styler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
157 changes: 157 additions & 0 deletions test/style/comment_directives_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# 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 of atoms" do
assert_style(
"""
# styler:sort
[
:c,
:b,
:a
]
""",
"""
# styler:sort
[
:a,
:b,
:c
]
"""
)
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

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

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