zf

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

source_file.ex (4231B)


      1 defmodule Credo.SourceFile do
      2   @moduledoc """
      3   `SourceFile` structs represent a source file in the codebase.
      4   """
      5 
      6   @type t :: %__MODULE__{
      7           filename: nil | String.t(),
      8           hash: String.t(),
      9           status: :valid | :invalid | :timed_out
     10         }
     11 
     12   alias Credo.Service.SourceFileAST
     13   alias Credo.Service.SourceFileLines
     14   alias Credo.Service.SourceFileSource
     15 
     16   defstruct filename: nil,
     17             hash: nil,
     18             status: nil
     19 
     20   defimpl Inspect, for: __MODULE__ do
     21     def inspect(source_file, _opts) do
     22       "%SourceFile<#{source_file.filename}>"
     23     end
     24   end
     25 
     26   @doc """
     27   Returns a `SourceFile` struct for the given `source` code and `filename`.
     28   """
     29   def parse(source, filename) do
     30     filename = Path.relative_to_cwd(filename)
     31 
     32     lines = Credo.Code.to_lines(source)
     33 
     34     {valid, ast} =
     35       case Credo.Code.ast(source) do
     36         {:ok, ast} ->
     37           {true, ast}
     38 
     39         {:error, _errors} ->
     40           {false, []}
     41       end
     42 
     43     hash =
     44       :sha256
     45       |> :crypto.hash(source)
     46       |> Base.encode16()
     47 
     48     source_file = %Credo.SourceFile{
     49       filename: filename,
     50       hash: hash,
     51       status: if(valid, do: :valid, else: :invalid)
     52     }
     53 
     54     SourceFileAST.put(source_file, ast)
     55     SourceFileLines.put(source_file, lines)
     56     SourceFileSource.put(source_file, source)
     57 
     58     source_file
     59   end
     60 
     61   @spec timed_out(String.t()) :: t
     62   def timed_out(filename) do
     63     filename = Path.relative_to_cwd(filename)
     64 
     65     %Credo.SourceFile{
     66       filename: filename,
     67       hash: "timed_out:#{filename}",
     68       status: :timed_out
     69     }
     70   end
     71 
     72   @doc "Returns the AST for the given `source_file`."
     73   def ast(source_file)
     74 
     75   def ast(%__MODULE__{} = source_file) do
     76     case SourceFileAST.get(source_file) do
     77       {:ok, ast} ->
     78         ast
     79 
     80       _ ->
     81         raise "Could not get source from ETS: #{source_file.filename}"
     82     end
     83   end
     84 
     85   @doc "Returns the lines of source code for the given `source_file`."
     86   def lines(source_file)
     87 
     88   def lines(%__MODULE__{} = source_file) do
     89     case SourceFileLines.get(source_file) do
     90       {:ok, lines} ->
     91         lines
     92 
     93       _ ->
     94         raise "Could not get source from ETS: #{source_file.filename}"
     95     end
     96   end
     97 
     98   @doc "Returns the source code for the given `source_file`."
     99   def source(source_file)
    100 
    101   def source(%__MODULE__{} = source_file) do
    102     case SourceFileSource.get(source_file) do
    103       {:ok, source} ->
    104         source
    105 
    106       _ ->
    107         raise "Could not get source from ETS: #{source_file.filename}"
    108     end
    109   end
    110 
    111   @doc "Returns the source code and filename for the given `source_file_or_source`."
    112   def source_and_filename(source_file_or_source, default_filename \\ "nofilename")
    113 
    114   def source_and_filename(%__MODULE__{filename: filename} = source_file, _default_filename) do
    115     {source(source_file), filename}
    116   end
    117 
    118   def source_and_filename(source, default_filename) when is_binary(source) do
    119     {source, default_filename}
    120   end
    121 
    122   @doc """
    123   Returns the line at the given `line_no`.
    124 
    125   NOTE: `line_no` is a 1-based index.
    126   """
    127   def line_at(%__MODULE__{} = source_file, line_no) do
    128     source_file
    129     |> lines()
    130     |> Enum.find_value(&find_line_at(&1, line_no))
    131   end
    132 
    133   defp find_line_at({line_no, text}, line_no), do: text
    134   defp find_line_at(_, _), do: nil
    135 
    136   @doc """
    137   Returns the snippet at the given `line_no` between `column1` and `column2`.
    138 
    139   NOTE: `line_no` is a 1-based index.
    140   """
    141   def line_at(%__MODULE__{} = source_file, line_no, column1, column2) do
    142     source_file
    143     |> line_at(line_no)
    144     |> String.slice(column1 - 1, column2 - column1)
    145   end
    146 
    147   @doc """
    148   Returns the column of the given `trigger` inside the given line.
    149 
    150   NOTE: Both `line_no` and the returned index are 1-based.
    151   """
    152   def column(source_file, line_no, trigger)
    153 
    154   def column(%__MODULE__{} = source_file, line_no, trigger)
    155       when is_binary(trigger) or is_atom(trigger) do
    156     line = line_at(source_file, line_no)
    157 
    158     regexed =
    159       trigger
    160       |> to_string
    161       |> Regex.escape()
    162 
    163     case Regex.run(~r/(\b|\(|\)|\,)(#{regexed})(\b|\(|\)|\,)/, line, return: :index) do
    164       nil ->
    165         nil
    166 
    167       [_, _, {regexed_col, _regexed_length}, _] ->
    168         regexed_col + 1
    169     end
    170   end
    171 
    172   def column(_, _, _), do: nil
    173 end