Reputation: 61
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
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
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