From 211d561068069bd91c22e8629e62e6c14a28b1ee Mon Sep 17 00:00:00 2001 From: Matt Enlow Date: Thu, 28 Mar 2024 11:15:07 -0600 Subject: [PATCH] implement string sigil rewrites --- README.md | 1 + lib/style/single_node.ex | 46 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 49ef2452..69dc6229 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Disabling the rules means updating your `.credo.exs` depending on your configura {Credo.Check.Readability.SinglePipe, false}, # **potentially breaks compilation** - see **Troubleshooting** section below {Credo.Check.Readability.StrictModuleLayout, false}, +{Credo.Check.Readability.StringSigils, false}, {Credo.Check.Readability.UnnecessaryAliasExpansion, false}, {Credo.Check.Readability.WithSingleClause, false}, {Credo.Check.Refactor.CaseTrivialMatches, false}, diff --git a/lib/style/single_node.ex b/lib/style/single_node.ex index a35bdee9..e74bd43c 100644 --- a/lib/style/single_node.ex +++ b/lib/style/single_node.ex @@ -18,6 +18,7 @@ defmodule Styler.Style.SingleNode do * Credo.Check.Readability.LargeNumbers * Credo.Check.Readability.ParenthesesOnZeroArityDefs * Credo.Check.Readability.PreferImplicitTry + * Credo.Check.Readability.StringSigils * Credo.Check.Readability.WithSingleClause * Credo.Check.Refactor.CaseTrivialMatches * Credo.Check.Refactor.CondStatements @@ -29,15 +30,52 @@ defmodule Styler.Style.SingleNode do alias Styler.Zipper + + def run({node, meta}, ctx), do: {:cont, {style(node), meta}, ctx} # as of 1.15, elixir's formatter takes care of this for us. if Version.match?(System.version(), "< 1.15.0-dev") do # 'charlist' => ~c"charlist" - defp style({:__block__, meta, [chars]} = node) when is_list(chars) do - if meta[:delimiter] == "'", - do: {:sigil_c, Keyword.put(meta, :delimiter, "\""), [{:<<>>, [line: meta[:line]], [List.to_string(chars)]}, []]}, - else: node + defp style({:__block__, [{:delimiter, ~s|"|} | meta], [chars]} = node) when is_list(chars), + do: {:sigil_c, [{:delimiter, ~s|"|} | meta], [{:<<>>, [line: meta[:line]], [List.to_string(chars)]}, []]} + end + + defp style({:__block__, [{:delimiter, ~s|"|} | meta], [string]} = node) when is_binary(string) do + # @TODO this'll run a regex against every double-quote delimited string in the codebase. + # is there a faster way to do this? is it too expensive vis-a-vis styler's goal of speed? + if string =~ ~r/".*".*".*"/ do + frequencies = + string + |> String.codepoints() + |> Stream.filter(& &1 in [")", "]", "}", "|", ">", ~s|"|]) + |> Enum.frequencies() + + # choose whichever delimiter has the least occurences, ties being broken by delimiter_priority + {closer, _} = + %{~s|"| => 0, ")" => 0, "]" => 0, "}" => 0, "|" => 0, ">" => 0} + |> Map.merge(frequencies) + |> Enum.min_by(fn + {~s|"|, count} -> {count, 0} + {")", count} -> {count, 1} + {"]", count} -> {count, 2} + {"}", count} -> {count, 3} + {"|", count} -> {count, 4} + {">", count} -> {count, 5} + end) + + delimiter = + case closer do + ")" -> "(" + "]" -> "[" + "}" -> "{" + ">" -> "<" + x -> x + end + + {:sigil_s, [{:delimiter, delimiter} | meta], [{:<<>>, [line: meta[:line]], [string]}, []]} + else + node end end