Add deprecation hint about `@lint` attributes

defmodule Credo.Check.Runner do
  alias Credo.CLI.Output.UI
  alias Credo.Config
  alias Credo.SourceFile
  alias Credo.Service.SourceFileIssues

  @doc false
  def run(source_files, config) when is_list(source_files) do
    {_time_run_on_all, source_files_after_run_on_all} =
      :timer.tc fn ->
        run_checks_that_run_on_all(source_files, config)
      end

    {_time_run, source_files} =
      :timer.tc fn ->
        source_files_after_run_on_all
        |> Enum.map(&Task.async(fn -> run(&1, config) end))
        |> Enum.map(&Task.await(&1, :infinity))
      end

    {source_files, config}
  end
  def run(%SourceFile{} = source_file, config) do
    checks =
      config
      |> Config.checks
      |> Enum.reject(&run_on_all_check?/1)

    issues = run_checks(source_file, checks, config)

    %SourceFile{source_file | issues: source_file.issues ++ issues}
  end

  @doc """
  Prepares the Config struct based on a given list of `source_files`.
  """
  def prepare_config(config) do
    prepare_config(config, config.source_files)
  end
  def prepare_config(config, source_files) do
    # TODO: remove prepare_config/2
    config
    |> set_lint_attributes(source_files)
    |> exclude_low_priority_checks(config.min_priority - 9)
    |> exclude_checks_based_on_elixir_version
  end

  defp set_lint_attributes(config, source_files) do
    lint_attribute_map =
      source_files
      |> run_linter_attribute_reader(config)
      |> Enum.reduce(%{}, fn(source_file, memo) ->
          # TODO: we should modify the config "directly" instead of going
          # through the SourceFile
          Map.put(memo, source_file.filename, source_file.lint_attributes)
        end)

    if Map.size(lint_attribute_map) > 0 do
      Credo.CLI.Output.UI.warn ""
      Credo.CLI.Output.UI.warn [:orange,
        "@lint attributes will be deprecated in the Credo v0.8 because they trigger\n",
        "compiler warnings on Elixir v1.4.\n\n",
        "Please consider reporting the cases where you needed @lint attributes\n",
        "to help us devise a new solution: https://github.com/rrrene/credo/issues/new"]
      Credo.CLI.Output.UI.warn ""
    end

    %Config{config | lint_attribute_map: lint_attribute_map}
  end

  defp run_linter_attribute_reader(source_files, config) do
    checks = [{Credo.Check.FindLintAttributes}]

    Enum.reduce(checks, source_files, fn(check_tuple, source_files) ->
      run_check(check_tuple, source_files, config)
    end)
  end

  defp exclude_low_priority_checks(config, below_priority) do
    checks =
      Enum.reject(config.checks, fn
        ({check}) -> check.base_priority < below_priority
        ({_check, false}) -> true
        ({check, opts}) ->
          (opts[:priority] || check.base_priority) < below_priority
      end)

    %Config{config | checks: checks}
  end

  defp exclude_checks_based_on_elixir_version(config) do
    version = System.version()
    skipped_checks = Enum.reject(config.checks, &matches_requirement?(&1, version))
    checks = Enum.filter(config.checks, &matches_requirement?(&1, version))

    %Config{config | checks: checks, skipped_checks: skipped_checks}
  end

  defp matches_requirement?({check, _}, version) do
    matches_requirement?({check}, version)
  end
  defp matches_requirement?({check}, version) do
    Version.match?(version, check.elixir_version)
  end

  defp run_checks_that_run_on_all(source_files, config) do
    checks =
      config
      |> Config.checks
      |> Enum.filter(&run_on_all_check?/1)

    checks
    |> Enum.map(&Task.async(fn ->
        run_check(&1, source_files, config)
      end))
    |> Enum.each(&Task.await(&1, :infinity))

    SourceFileIssues.update_in_source_files(source_files)
  end

  defp run_checks(%SourceFile{} = source_file, checks, config) when is_list(checks) do
    Enum.flat_map(checks, &run_check(&1, source_file, config))
  end

  defp run_check({_check, false}, source_files, _config) when is_list(source_files) do
    source_files
  end
  defp run_check({_check, false}, _source_file, _config) do
    []
  end
  defp run_check({check}, source_file, config) do
    run_check({check, []}, source_file, config)
  end
  defp run_check({check, params}, source_file, config) do
    try do
      check.run(source_file, params)
    rescue
      error ->
        warn_about_failed_run(check, source_file)
        if config.crash_on_error do
          reraise error, System.stacktrace()
        else
          []
        end
    end
  end

  defp warn_about_failed_run(check, %SourceFile{} = source_file) do
    UI.warn("Error while running #{check} on #{source_file.filename}")
  end
  defp warn_about_failed_run(check, _) do
    UI.warn("Error while running #{check}")
  end

  defp run_on_all_check?({check}), do: check.run_on_all?
  defp run_on_all_check?({check, _params}), do: check.run_on_all?
end

defmodule Credo.Check.Runner do
  alias Credo.CLI.Output.UI
  alias Credo.Config
  alias Credo.SourceFile
  alias Credo.Service.SourceFileIssues

  @doc false
  def run(source_files, config) when is_list(source_files) do
    {_time_run_on_all, source_files_after_run_on_all} =
      :timer.tc fn ->
        run_checks_that_run_on_all(source_files, config)
      end

    {_time_run, source_files} =
      :timer.tc fn ->
        source_files_after_run_on_all
        |> Enum.map(&Task.async(fn -> run(&1, config) end))
        |> Enum.map(&Task.await(&1, :infinity))
      end

    {source_files, config}
  end
  def run(%SourceFile{} = source_file, config) do
    checks =
      config
      |> Config.checks
      |> Enum.reject(&run_on_all_check?/1)

    issues = run_checks(source_file, checks, config)

    %SourceFile{source_file | issues: source_file.issues ++ issues}
  end

  @doc """
  Prepares the Config struct based on a given list of `source_files`.
  """
  def prepare_config(config) do
    prepare_config(config, config.source_files)
  end
  def prepare_config(config, source_files) do
    # TODO: remove prepare_config/2
    config
    |> set_lint_attributes(source_files)
    |> exclude_low_priority_checks(config.min_priority - 9)
    |> exclude_checks_based_on_elixir_version
  end

  defp set_lint_attributes(config, source_files) do
    lint_attribute_map =
      source_files
      |> run_linter_attribute_reader(config)
      |> Enum.reduce(%{}, fn(source_file, memo) ->
          # TODO: we should modify the config "directly" instead of going
          # through the SourceFile
          Map.put(memo, source_file.filename, source_file.lint_attributes)
        end)

    %Config{config | lint_attribute_map: lint_attribute_map}
  end

  defp run_linter_attribute_reader(source_files, config) do
    checks = [{Credo.Check.FindLintAttributes}]

    Enum.reduce(checks, source_files, fn(check_tuple, source_files) ->
      run_check(check_tuple, source_files, config)
    end)
  end

  defp exclude_low_priority_checks(config, below_priority) do
    checks =
      Enum.reject(config.checks, fn
        ({check}) -> check.base_priority < below_priority
        ({_check, false}) -> true
        ({check, opts}) ->
          (opts[:priority] || check.base_priority) < below_priority
      end)

    %Config{config | checks: checks}
  end

  defp exclude_checks_based_on_elixir_version(config) do
    version = System.version()
    skipped_checks = Enum.reject(config.checks, &matches_requirement?(&1, version))
    checks = Enum.filter(config.checks, &matches_requirement?(&1, version))

    %Config{config | checks: checks, skipped_checks: skipped_checks}
  end

  defp matches_requirement?({check, _}, version) do
    matches_requirement?({check}, version)
  end
  defp matches_requirement?({check}, version) do
    Version.match?(version, check.elixir_version)
  end

  defp run_checks_that_run_on_all(source_files, config) do
    checks =
      config
      |> Config.checks
      |> Enum.filter(&run_on_all_check?/1)

    checks
    |> Enum.map(&Task.async(fn ->
        run_check(&1, source_files, config)
      end))
    |> Enum.each(&Task.await(&1, :infinity))

    SourceFileIssues.update_in_source_files(source_files)
  end

  defp run_checks(%SourceFile{} = source_file, checks, config) when is_list(checks) do
    Enum.flat_map(checks, &run_check(&1, source_file, config))
  end

  defp run_check({_check, false}, source_files, _config) when is_list(source_files) do
    source_files
  end
  defp run_check({_check, false}, _source_file, _config) do
    []
  end
  defp run_check({check}, source_file, config) do
    run_check({check, []}, source_file, config)
  end
  defp run_check({check, params}, source_file, config) do
    try do
      check.run(source_file, params)
    rescue
      error ->
        warn_about_failed_run(check, source_file)
        if config.crash_on_error do
          reraise error, System.stacktrace()
        else
          []
        end
    end
  end

  defp warn_about_failed_run(check, %SourceFile{} = source_file) do
    UI.warn("Error while running #{check} on #{source_file.filename}")
  end
  defp warn_about_failed_run(check, _) do
    UI.warn("Error while running #{check}")
  end

  defp run_on_all_check?({check}), do: check.run_on_all?
  defp run_on_all_check?({check, _params}), do: check.run_on_all?
end


List cookies

List cookies

defmodule Exsoda.AuthServer do
  use GenServer

  @clear_timeout 10 * 60 * 1000

  defmodule State do
    defstruct pending: %{}, cookies: %{}
  end

  def start_link() do
    GenServer.start_link(__MODULE__, [], [name: __MODULE__])
  end

  def init([]) do
    :timer.apply_interval(@clear_timeout, __MODULE__, :clear_cookies, [])
    {:ok, %State{}}
  end

  def get_cookie(opts) do
    GenServer.call(__MODULE__, {:get_cookie, opts})
  end

  def list_cookies() do
    GenServer.call(__MODULE__, :list_cookies)
  end

  def clear_cookies() do
    GenServer.cast(__MODULE__, :clear_cookies)
  end

  def handle_call({:get_cookie, opts}, reply, %State{pending: pending, cookies: cookies} = state) do
    key = Map.take(opts, [:host, :domain, :api_root, :protocol, :spoof])
    case cookies[key] do
      nil ->
        {worker, _ref} = spawn_monitor(fn ->
          result = Exsoda.Http.get_cookie_impl(opts)
          GenServer.cast(__MODULE__, {:cookie_result, self(), reply, key, result})
        end)
        {:noreply, %State{state | pending: Map.put(pending, worker, reply)}}
      cookie ->
        {:reply, {:ok, cookie}, state}
    end
  end

  def handle_call(:list_cookies, _, %State{cookies: cookies} = state) do
    {:reply, cookies, state}
  end

  def handle_cast(:clear_cookies, %State{} = state) do
    {:noreply, %State{state | cookies: %{}}}
  end

  def handle_cast({:cookie_result, worker, reply, key, {:ok, cookie} = response}, %State{pending: pending, cookies: cookies} = state) do
    GenServer.reply(reply, response)
    {:noreply, %State{state | pending: Map.delete(pending, worker),
                              cookies: Map.put(cookies, key, cookie)}}
  end
  def handle_cast({:cookie_result, worker, reply, _key, response}, %State{pending: pending} = state) do
    GenServer.reply(reply, response)
    {:noreply, %State{state | pending: Map.delete(pending, worker)}}
  end

  def handle_cast({:DOWN, _ref, _type, pid, info}, %State{pending: pending} = state) do
    case Map.get(pending, pid) do
      nil ->
        {:noreply, state}
      reply ->
        GenServer.reply(reply, {:error, info})
        {:noreply, %State{state | pending: Map.delete(pending, pid)}}
    end
  end
end

defmodule Exsoda.AuthServer do
  use GenServer

  @clear_timeout 10 * 60 * 1000

  defmodule State do
    defstruct pending: %{}, cookies: %{}
  end

  def start_link() do
    GenServer.start_link(__MODULE__, [], [name: __MODULE__])
  end

  def init([]) do
    :timer.apply_interval(@clear_timeout, __MODULE__, :clear_cookies, [])
    {:ok, %State{}}
  end

  def get_cookie(opts) do
    GenServer.call(__MODULE__, {:get_cookie, opts})
  end

  def clear_cookies() do
    GenServer.cast(__MODULE__, :clear_cookies)
  end

  def handle_call({:get_cookie, opts}, reply, %State{pending: pending, cookies: cookies} = state) do
    key = Map.take(opts, [:host, :domain, :api_root, :protocol, :spoof])
    case cookies[key] do
      nil ->
        {worker, _ref} = spawn_monitor(fn ->
          result = Exsoda.Http.get_cookie_impl(opts)
          GenServer.cast(__MODULE__, {:cookie_result, self(), reply, key, result})
        end)
        {:noreply, %State{state | pending: Map.put(pending, worker, reply)}}
      cookie ->
        {:reply, {:ok, cookie}, state}
    end
  end

  def handle_cast(:clear_cookies, %State{} = state) do
    {:noreply, %State{state | cookies: %{}}}
  end

  def handle_cast({:cookie_result, worker, reply, key, {:ok, cookie} = response}, %State{pending: pending, cookies: cookies} = state) do
    GenServer.reply(reply, response)
    {:noreply, %State{state | pending: Map.delete(pending, worker),
                              cookies: Map.put(cookies, key, cookie)}}
  end
  def handle_cast({:cookie_result, worker, reply, _key, response}, %State{pending: pending} = state) do
    GenServer.reply(reply, response)
    {:noreply, %State{state | pending: Map.delete(pending, worker)}}
  end

  def handle_cast({:DOWN, _ref, _type, pid, info}, %State{pending: pending} = state) do
    case Map.get(pending, pid) do
      nil ->
        {:noreply, state}
      reply ->
        GenServer.reply(reply, {:error, info})
        {:noreply, %State{state | pending: Map.delete(pending, pid)}}
    end
  end
end


Subjective word ordering

Subjective word ordering

Subjectively, I find this to read better. Perhaps it's part of my North
American dialect?

defmodule Processes do
  use Koans

  koan "Tests run in a process" do
    assert Process.alive?(self()) == ___
  end

  koan "You can ask a process to introduce itself" do
    information = Process.info(self())

    assert information[:status] == ___
  end

  koan "You can send messages to any process you want" do
    send self(), "hola!"
    assert_receive ___
  end

  koan "A common pattern is to include the sender in the message" do
    pid = spawn(fn -> receive do
                         {:hello, sender} -> send sender, :how_are_you?
                         _ -> assert false
                      end
                 end)

    send pid, {:hello, self()}
    assert_receive ___
  end

  koan "Waiting for a message can get boring" do
    parent = self()
    spawn(fn -> receive do
                after
                  5 -> send parent, {:waited_too_long, "I am impatient"}
                end
           end)

    assert_receive ___
  end

  koan "Killing a process will terminate it" do
    pid = spawn(fn -> Process.exit(self(), :kill) end)
    :timer.sleep(500)
    assert Process.alive?(pid) == ___
  end

  koan "You can also terminate processes other than yourself" do
    pid = spawn(fn -> receive do end end)

    assert Process.alive?(pid) == ___
    Process.exit(pid, :kill)
    assert Process.alive?(pid) == ___
  end

  koan "Trapping will allow you to react to someone terminating the process" do
    parent = self()
    pid = spawn(fn ->
                      Process.flag(:trap_exit, true)
                      send parent, :ready
                      receive do
                        {:EXIT, _pid, reason} -> send parent, {:exited, reason}
                      end
    end)
    wait()
    Process.exit(pid, :random_reason)

    assert_receive ___
  end

  koan "Trying to quit normally has no effect" do
    pid = spawn(fn -> receive do
                      end
                end)
    Process.exit(pid, :normal)
    assert Process.alive?(pid) == ___
  end

  koan "Exiting yourself on the other hand DOES terminate you" do
    pid = spawn(fn -> receive do
                        :bye -> Process.exit(self(), :normal)
                      end
                end)

    send pid, :bye
    :timer.sleep(100)
    assert Process.alive?(pid) == ___
  end

  koan "Parent processes can be informed about exiting children, if they trap and link" do
    parent = self()
    spawn(fn ->
            Process.flag(:trap_exit, true)
            spawn_link(fn -> Process.exit(self(), :normal) end)
            receive do
              {:EXIT, _pid ,reason} -> send parent, {:exited, reason}
            end
     end)

    assert_receive ___
  end

  koan "If you monitor your children, you'll be automatically informed for their depature" do
    parent = self()
    spawn(fn ->
            spawn_monitor(fn -> Process.exit(self(), :normal) end)
            receive do
              {:DOWN, _ref, :process, _pid, reason} -> send parent, {:exited, reason}
            end
     end)

    assert_receive ___
  end

  def wait do
    receive do
      :ready -> true
    end
  end
end

defmodule Processes do
  use Koans

  koan "Tests run in a process" do
    assert Process.alive?(self()) == ___
  end

  koan "You can ask a process to introduce itself" do
    information = Process.info(self())

    assert information[:status] == ___
  end

  koan "You can send messages to any process you want" do
    send self(), "hola!"
    assert_receive ___
  end

  koan "A common pattern is to include the sender in the message" do
    pid = spawn(fn -> receive do
                         {:hello, sender} -> send sender, :how_are_you?
                         _ -> assert false
                      end
                 end)

    send pid, {:hello, self()}
    assert_receive ___
  end

  koan "Waiting for a message can get boring" do
    parent = self()
    spawn(fn -> receive do
                after
                  5 -> send parent, {:waited_too_long, "I am impatient"}
                end
           end)

    assert_receive ___
  end

  koan "Killing a process will terminate it" do
    pid = spawn(fn -> Process.exit(self(), :kill) end)
    :timer.sleep(500)
    assert Process.alive?(pid) == ___
  end

  koan "You can also terminate other processes than yourself" do
    pid = spawn(fn -> receive do end end)

    assert Process.alive?(pid) == ___
    Process.exit(pid, :kill)
    assert Process.alive?(pid) == ___
  end

  koan "Trapping will allow you to react to someone terminating the process" do
    parent = self()
    pid = spawn(fn ->
                      Process.flag(:trap_exit, true)
                      send parent, :ready
                      receive do
                        {:EXIT, _pid, reason} -> send parent, {:exited, reason}
                      end
    end)
    wait()
    Process.exit(pid, :random_reason)

    assert_receive ___
  end

  koan "Trying to quit normally has no effect" do
    pid = spawn(fn -> receive do
                      end
                end)
    Process.exit(pid, :normal)
    assert Process.alive?(pid) == ___
  end

  koan "Exiting yourself on the other hand DOES terminate you" do
    pid = spawn(fn -> receive do
                        :bye -> Process.exit(self(), :normal)
                      end
                end)

    send pid, :bye
    :timer.sleep(100)
    assert Process.alive?(pid) == ___
  end

  koan "Parent processes can be informed about exiting children, if they trap and link" do
    parent = self()
    spawn(fn ->
            Process.flag(:trap_exit, true)
            spawn_link(fn -> Process.exit(self(), :normal) end)
            receive do
              {:EXIT, _pid ,reason} -> send parent, {:exited, reason}
            end
     end)

    assert_receive ___
  end

  koan "If you monitor your children, you'll be automatically informed for their depature" do
    parent = self()
    spawn(fn ->
            spawn_monitor(fn -> Process.exit(self(), :normal) end)
            receive do
              {:DOWN, _ref, :process, _pid, reason} -> send parent, {:exited, reason}
            end
     end)

    assert_receive ___
  end

  def wait do
    receive do
      :ready -> true
    end
  end
end