zf

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

code.ex (5421B)


      1 defmodule Credo.Code do
      2   @moduledoc """
      3   `Credo.Code` contains a lot of utility or helper functions that deal with the
      4   analysis of - you guessed it - code.
      5 
      6   Whenever a function serves a general purpose in this area, e.g. getting the
      7   value of a module attribute inside a given module, we want to extract that
      8   function and put it in the `Credo.Code` namespace, so others can utilize them
      9   without reinventing the wheel.
     10   """
     11 
     12   alias Credo.Code.Charlists
     13   alias Credo.Code.Heredocs
     14   alias Credo.Code.Sigils
     15   alias Credo.Code.Strings
     16 
     17   alias Credo.SourceFile
     18 
     19   defmodule ParserError do
     20     @moduledoc """
     21     This is an internal `Issue` raised by Credo when it finds itself unable to
     22     parse the source code in a file.
     23     """
     24   end
     25 
     26   @doc """
     27   Prewalks a given `Credo.SourceFile`'s AST or a given AST.
     28 
     29   Technically this is just a wrapper around `Macro.prewalk/3`.
     30   """
     31   def prewalk(ast_or_source_file, fun, accumulator \\ [])
     32 
     33   def prewalk(%SourceFile{} = source_file, fun, accumulator) do
     34     source_file
     35     |> SourceFile.ast()
     36     |> prewalk(fun, accumulator)
     37   end
     38 
     39   def prewalk(source_ast, fun, accumulator) do
     40     {_, accumulated} = Macro.prewalk(source_ast, accumulator, fun)
     41 
     42     accumulated
     43   end
     44 
     45   @doc """
     46   Postwalks a given `Credo.SourceFile`'s AST or a given AST.
     47 
     48   Technically this is just a wrapper around `Macro.postwalk/3`.
     49   """
     50   def postwalk(ast_or_source_file, fun, accumulator \\ [])
     51 
     52   def postwalk(%SourceFile{} = source_file, fun, accumulator) do
     53     source_file
     54     |> SourceFile.ast()
     55     |> postwalk(fun, accumulator)
     56   end
     57 
     58   def postwalk(source_ast, fun, accumulator) do
     59     {_, accumulated} = Macro.postwalk(source_ast, accumulator, fun)
     60 
     61     accumulated
     62   end
     63 
     64   @doc """
     65   Returns an AST for a given `String` or `Credo.SourceFile`.
     66   """
     67   def ast(string_or_source_file)
     68 
     69   def ast(%SourceFile{filename: filename} = source_file) do
     70     source_file
     71     |> SourceFile.source()
     72     |> ast(filename)
     73   end
     74 
     75   @doc false
     76   def ast(source, filename \\ "nofilename") when is_binary(source) do
     77     case Code.string_to_quoted(source, line: 1, columns: true, file: filename) do
     78       {:ok, value} ->
     79         {:ok, value}
     80 
     81       {:error, error} ->
     82         {:error, [issue_for(error, filename)]}
     83     end
     84   rescue
     85     e in UnicodeConversionError ->
     86       {:error, [issue_for({1, e.message, nil}, filename)]}
     87   end
     88 
     89   defp issue_for({line_no, error_message, _}, filename) do
     90     %Credo.Issue{
     91       check: ParserError,
     92       category: :error,
     93       filename: filename,
     94       message: error_message,
     95       line_no: line_no
     96     }
     97   end
     98 
     99   @doc """
    100   Converts a String or `Credo.SourceFile` into a List of tuples of `{line_no, line}`.
    101   """
    102   def to_lines(string_or_source_file)
    103 
    104   def to_lines(%SourceFile{} = source_file) do
    105     source_file
    106     |> SourceFile.source()
    107     |> to_lines()
    108   end
    109 
    110   def to_lines(source) when is_binary(source) do
    111     source
    112     |> String.split("\n")
    113     |> Enum.with_index()
    114     |> Enum.map(fn {line, i} -> {i + 1, line} end)
    115   end
    116 
    117   @doc """
    118   Converts a String or `Credo.SourceFile` into a List of tokens using the `:elixir_tokenizer`.
    119   """
    120   def to_tokens(string_or_source_file)
    121 
    122   def to_tokens(%SourceFile{} = source_file) do
    123     source_file
    124     |> SourceFile.source()
    125     |> to_tokens(source_file.filename)
    126   end
    127 
    128   def to_tokens(source, filename \\ "nofilename") when is_binary(source) do
    129     source
    130     |> String.to_charlist()
    131     |> :elixir_tokenizer.tokenize(1, file: filename)
    132     |> case do
    133       # Elixir < 1.6
    134       {_, _, _, tokens} ->
    135         tokens
    136 
    137       # Elixir >= 1.6
    138       {:ok, tokens} ->
    139         tokens
    140 
    141       # Elixir >= 1.13
    142       {:ok, _, _, _, tokens} ->
    143         tokens
    144 
    145       {:error, _, _, _, tokens} ->
    146         tokens
    147     end
    148   end
    149 
    150   @doc """
    151   Returns true if the given `child` AST node is part of the larger
    152   `parent` AST node.
    153   """
    154   def contains_child?(parent, child) do
    155     Credo.Code.prewalk(parent, &find_child(&1, &2, child), false)
    156   end
    157 
    158   defp find_child({parent, _meta, child}, _acc, child), do: {parent, true}
    159 
    160   defp find_child(parent, acc, child), do: {parent, acc || parent == child}
    161 
    162   @doc """
    163   Takes a SourceFile and returns its source code stripped of all Strings and
    164   Sigils.
    165   """
    166   def clean_charlists_strings_and_sigils(source_file_or_source) do
    167     {_source, filename} = Credo.SourceFile.source_and_filename(source_file_or_source)
    168 
    169     source_file_or_source
    170     |> Sigils.replace_with_spaces(" ", " ", filename)
    171     |> Strings.replace_with_spaces(" ", " ", filename)
    172     |> Heredocs.replace_with_spaces(" ", " ", "", filename)
    173     |> Charlists.replace_with_spaces(" ", " ", filename)
    174   end
    175 
    176   @doc """
    177   Takes a SourceFile and returns its source code stripped of all Strings, Sigils
    178   and code comments.
    179   """
    180   def clean_charlists_strings_sigils_and_comments(source_file_or_source, sigil_replacement \\ " ") do
    181     {_source, filename} = Credo.SourceFile.source_and_filename(source_file_or_source)
    182 
    183     source_file_or_source
    184     |> Heredocs.replace_with_spaces(" ", " ", "", filename)
    185     |> Sigils.replace_with_spaces(sigil_replacement, " ", filename)
    186     |> Strings.replace_with_spaces(" ", " ", filename)
    187     |> Charlists.replace_with_spaces(" ", " ", filename)
    188     |> String.replace(~r/(\A|[^\?])#.+/, "\\1")
    189   end
    190 
    191   @doc """
    192   Returns an AST without its metadata.
    193   """
    194   def remove_metadata(ast) do
    195     Macro.prewalk(ast, &Macro.update_meta(&1, fn _meta -> [] end))
    196   end
    197 end