zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

string_sigils.ex (3823B)


      1 defmodule Credo.Check.Readability.StringSigils do
      2   alias Credo.Code.Heredocs
      3   alias Credo.SourceFile
      4 
      5   use Credo.Check,
      6     base_priority: :low,
      7     param_defaults: [
      8       maximum_allowed_quotes: 3
      9     ],
     10     explanations: [
     11       check: ~S"""
     12       If you used quoted strings that contain quotes, you might want to consider
     13       switching to the use of sigils instead.
     14 
     15           # okay
     16 
     17           "<a href=\"http://elixirweekly.net\">#\{text}</a>"
     18 
     19           # not okay, lots of escaped quotes
     20 
     21           "<a href=\"http://elixirweekly.net\" target=\"_blank\">#\{text}</a>"
     22 
     23           # refactor to
     24 
     25           ~S(<a href="http://elixirweekly.net" target="_blank">#\{text}</a>)
     26 
     27       This allows us to remove the noise which results from the need to escape
     28       quotes within quotes.
     29 
     30       Like all `Readability` issues, this one is not a technical concern.
     31       But you can improve the odds of others reading and liking your code by making
     32       it easier to follow.
     33       """,
     34       params: [
     35         maximum_allowed_quotes: "The maximum amount of escaped quotes you want to tolerate."
     36       ]
     37     ]
     38 
     39   @quote_codepoint 34
     40 
     41   @doc false
     42   @impl true
     43   def run(%SourceFile{} = source_file, params) do
     44     issue_meta = IssueMeta.for(source_file, params)
     45 
     46     maximum_allowed_quotes = Params.get(params, :maximum_allowed_quotes, __MODULE__)
     47 
     48     case remove_heredocs_and_convert_to_ast(source_file) do
     49       {:ok, ast} ->
     50         Credo.Code.prewalk(ast, &traverse(&1, &2, issue_meta, maximum_allowed_quotes))
     51 
     52       {:error, errors} ->
     53         IO.warn("Unexpected error while parsing #{source_file.filename}: #{inspect(errors)}")
     54         []
     55     end
     56   end
     57 
     58   defp remove_heredocs_and_convert_to_ast(source_file) do
     59     source_file
     60     |> Heredocs.replace_with_spaces()
     61     |> Credo.Code.ast()
     62   end
     63 
     64   defp traverse(
     65          {maybe_sigil, meta, [str | rest_ast]} = ast,
     66          issues,
     67          issue_meta,
     68          maximum_allowed_quotes
     69        ) do
     70     line_no = meta[:line]
     71 
     72     cond do
     73       is_sigil(maybe_sigil) ->
     74         {rest_ast, issues}
     75 
     76       is_binary(str) ->
     77         {
     78           rest_ast,
     79           issues_for_string_literal(
     80             str,
     81             maximum_allowed_quotes,
     82             issues,
     83             issue_meta,
     84             line_no
     85           )
     86         }
     87 
     88       true ->
     89         {ast, issues}
     90     end
     91   end
     92 
     93   defp traverse(ast, issues, _issue_meta, _maximum_allowed_quotes) do
     94     {ast, issues}
     95   end
     96 
     97   defp is_sigil(maybe_sigil) when is_atom(maybe_sigil) do
     98     maybe_sigil
     99     |> Atom.to_string()
    100     |> String.starts_with?("sigil_")
    101   end
    102 
    103   defp is_sigil(_), do: false
    104 
    105   defp issues_for_string_literal(
    106          string,
    107          maximum_allowed_quotes,
    108          issues,
    109          issue_meta,
    110          line_no
    111        ) do
    112     if too_many_quotes?(string, maximum_allowed_quotes) do
    113       [issue_for(issue_meta, line_no, string, maximum_allowed_quotes) | issues]
    114     else
    115       issues
    116     end
    117   end
    118 
    119   defp too_many_quotes?(string, limit) do
    120     too_many_quotes?(string, 0, limit)
    121   end
    122 
    123   defp too_many_quotes?(_string, count, limit) when count > limit do
    124     true
    125   end
    126 
    127   defp too_many_quotes?(<<>>, _count, _limit) do
    128     false
    129   end
    130 
    131   defp too_many_quotes?(<<c::utf8, rest::binary>>, count, limit)
    132        when c == @quote_codepoint do
    133     too_many_quotes?(rest, count + 1, limit)
    134   end
    135 
    136   defp too_many_quotes?(<<_::utf8, rest::binary>>, count, limit) do
    137     too_many_quotes?(rest, count, limit)
    138   end
    139 
    140   defp too_many_quotes?(<<_::binary>>, _count, _limit) do
    141     false
    142   end
    143 
    144   defp issue_for(issue_meta, line_no, trigger, maximum_allowed_quotes) do
    145     format_issue(
    146       issue_meta,
    147       message:
    148         "More than #{maximum_allowed_quotes} quotes found inside string literal, consider using a sigil instead.",
    149       trigger: trigger,
    150       line_no: line_no
    151     )
    152   end
    153 end