zf

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

prefer_unquoted_atoms.ex (2847B)


      1 defmodule Credo.Check.Readability.PreferUnquotedAtoms do
      2   use Credo.Check,
      3     run_on_all: true,
      4     base_priority: :high,
      5     elixir_version: "< 1.7.0-dev",
      6     explanations: [
      7       check: """
      8       Prefer unquoted atoms unless quotes are necessary.
      9       This is helpful because a quoted atom can be easily mistaken for a string.
     10 
     11           # preferred
     12 
     13           :x
     14           [x: 1]
     15           %{x: 1}
     16 
     17           # NOT preferred
     18 
     19           :"x"
     20           ["x": 1]
     21           %{"x": 1}
     22 
     23       The primary case where this can become an issue is when using atoms or
     24       strings for keys in a Map or Keyword list.
     25 
     26       For example, this:
     27 
     28           %{"x": 1}
     29 
     30       Can easily be mistaken for this:
     31 
     32           %{"x" => 1}
     33 
     34       Because a string key cannot be used to access a value with the equivalent
     35       atom key, this can lead to subtle bugs which are hard to discover.
     36 
     37       Like all `Readability` issues, this one is not a technical concern.
     38       The code will behave identical in both ways.
     39       """
     40     ]
     41 
     42   @token_types [:atom_unsafe, :kw_identifier_unsafe]
     43 
     44   @doc false
     45   @impl true
     46   # TODO: consider for experimental check front-loader (tokens)
     47   def run(%SourceFile{} = source_file, params) do
     48     issue_meta = IssueMeta.for(source_file, params)
     49 
     50     source_file
     51     |> Credo.Code.to_tokens()
     52     |> Enum.reduce([], &find_issues(&1, &2, issue_meta))
     53     |> Enum.reverse()
     54   end
     55 
     56   for type <- @token_types do
     57     defp find_issues(
     58            {unquote(type), {line_no, column, _}, token},
     59            issues,
     60            issue_meta
     61          ) do
     62       case safe_atom_name(token) do
     63         nil ->
     64           issues
     65 
     66         atom ->
     67           [issue_for(issue_meta, atom, line_no, column) | issues]
     68       end
     69     end
     70   end
     71 
     72   defp find_issues(_token, issues, _issue_meta) do
     73     issues
     74   end
     75 
     76   # "safe atom" here refers to a quoted atom not containing an interpolation
     77   defp safe_atom_name(token) when is_list(token) do
     78     if Enum.all?(token, &is_binary/1) do
     79       token
     80       |> Enum.join()
     81       |> safe_atom_name()
     82     end
     83   end
     84 
     85   defp safe_atom_name(token) when is_binary(token) do
     86     ':#{token}'
     87     |> :elixir_tokenizer.tokenize(1, [])
     88     |> safe_atom_name(token)
     89   end
     90 
     91   defp safe_atom_name(_), do: nil
     92 
     93   # Elixir >= 1.6.0
     94   defp safe_atom_name({:ok, [{:atom, {_, _, _}, atom} | _]}, token) do
     95     if token == Atom.to_string(atom) do
     96       atom
     97     end
     98   end
     99 
    100   # Elixir <= 1.5.x
    101   defp safe_atom_name({:ok, _, _, [{:atom, _, atom} | _]}, token) do
    102     if token == Atom.to_string(atom) do
    103       atom
    104     end
    105   end
    106 
    107   defp issue_for(issue_meta, atom, line_no, column) do
    108     trigger = ~s[:"#{atom}"]
    109 
    110     format_issue(
    111       issue_meta,
    112       message: "Use unquoted atom `#{inspect(atom)}` rather than quoted atom `#{trigger}`.",
    113       trigger: trigger,
    114       line_no: line_no,
    115       column: column
    116     )
    117   end
    118 end