user3713237
user3713237

Reputation: 61

How to pattern match two lines in Elixir Credo

So I'm working a custom check which aim to "identify graphql type/field definition that missing description"

the code looks like this

    defmodule MockProject.MockModule do
      @moduledoc false

      node object1(:random_object) do
        @desc "random description"
        field :random_field, non_null(:random_name) do
          resolve &Resolver.RandomResolver.random_field/3
        end
      end

      @desc "random node description"
      node object2(:random_object) do
        @desc "random description"
        field :random_field, non_null(:random_name) do
          resolve &Resolver.RandomResolver.random_field/3
        end
      end
    end

I can easily find line that contains node or field or desc.

but I don't know how to find a line with 'node' which missing @desc in its previous line , is there a way to find the previous line for an ast?

Upvotes: 2

Views: 103

Answers (2)

Peaceful James
Peaceful James

Reputation: 2235

In my humble opinion, trying to do this with Credo is a masochistic exercise. Consider the following bit of valid code:

defmodule MyAppWeb.Schema.Finance.MoneyTypes do
  use Absinthe.Schema.Notation

  @desc "UNUSED 1"

  @desc "MONEY 1"
  object :money do
    description("MONEY 2")
    field :amount, :decimal
    @desc "CURRENCY 1"
    field :currency, :string do
      description("CURRENCY 2")
    end
  end
end

The credo checker will have to handle (possibly flag) unused @desc attributes, identify duplicate descriptions (which can use different syntax), and deal with the fact the you will have to consider AST tuples with first element in [:object, :node, :field], each of which may or may not have an associated do block.

It's not impossible but it's arduous and prone to breaking (if Absinthe introduces a new object syntax or description shorthand).

In short, I think what you are trying to do should be done using already well-developed GraphQL introspection tools.

Consider the following module:

defmodule MyAppWeb.Schema.IntrospectionUtils do
  @moduledoc """
  Convenience functions for inspecting the schema
  """

  @doc """
  iex> missing_desc_list(MyAppWeb.Schema)
      [...]
  """
  def missing_desc_list(schema_module) do
    schema_module
    |> type_names_and_descs()
    |> missing_descs()
  end

  defp type_names_and_descs(schema_module) do
    case " {__schema {types {name description fields {name description}}}} "
         |> Absinthe.run(schema_module) do
      {:ok, %{data: %{"__schema" => %{"types" => types}}}} -> types
      _ -> raise("Unable to get types name and descs map")
    end
  end

  defp missing_descs(types) do
    types
    |> Enum.reduce([], &missing_type_descs/2)
  end

  # ignore type names that start with double underscore
  defp missing_type_descs(%{"name" => "__" <> _}, acc), do: acc

  defp missing_type_descs(%{"name" => name, "description" => nil, "fields" => fields}, acc) do
    missing_field_descs(fields, ["type #{name} is missing a description" | acc], name)
  end

  defp missing_type_descs(%{"name" => name, "description" => desc, "fields" => fields}, acc)
       when is_binary(desc) do
    missing_field_descs(fields, acc, name)
  end

  defp missing_field_descs(nil, acc, _type_name), do: acc

  # ignore type names that start with double underscore
  defp missing_field_descs(_fields, acc, "__" <> _), do: acc

  defp missing_field_descs(fields, acc, type_name) when is_list(fields) do
    fields
    |> Enum.reduce(acc, &missing_field_descs(&1, &2, type_name))
  end

  defp missing_field_descs(%{"name" => name, "description" => nil}, acc, type_name) do
    ["field #{name} on type #{type_name} is missing a description" | acc]
  end

  defp missing_field_descs(_field, acc, _type_name), do: acc
end

It's not very elegant but it shows how you easily get a list of all missing documentation by spinning up your app with iex -S mix (or iex -S mix phx.server) and typing:

MyAppWeb.Schema.IntrospectionUtils.missing_desc_list(MyAppWeb.Schema)

Note that I have added some functions to "ignore" schema type names starting with 2 underscores __. This indicates a built-in GQ type and should not require a description.

Note also that my code is only illustrative of an approach and has not been tested or reviewed. It could probably be a lot better.

This approach can be used as part of CI or similar but it cannot be run like a normal Mix task or Credo checker because the app needs to be running for it to work.

Upvotes: 0

Hauleth
Hauleth

Reputation: 23556

No. What you need to do is to keep track of the last @desc encountered and then reset it each time you spot a field call (and emit the error when there is none).

Also fetching 1 previous line will not be enough, as you can do stuff like:

@desc "foo"
@another_attr 1
field :my_field, non_null(:val) do
  # …
end

And this will still be a correct way to define the description for :my_field.

Upvotes: 1

Related Questions