diff --git a/lib/tapioca/dsl/compilers/graphql_schema.rb b/lib/tapioca/dsl/compilers/graphql_schema.rb new file mode 100644 index 000000000..cc1f6cb07 --- /dev/null +++ b/lib/tapioca/dsl/compilers/graphql_schema.rb @@ -0,0 +1,70 @@ +# typed: strict +# frozen_string_literal: true + +begin + gem("graphql", ">= 1.13") + require "graphql" +rescue LoadError + return +end + +require "tapioca/dsl/helpers/graphql_type_helper" + +module Tapioca + module Dsl + module Compilers + # `Tapioca::Dsl::Compilers::GraphqlSchema` generates RBI files for subclasses of + # [`GraphQL::Schema`](https://graphql-ruby.org/api-doc/2.1.7/GraphQL/Schema). + # + # For example, with the following `GraphQL::Schema` subclass: + # + # ~~~rb + # class MySchema> < GraphQL::Schema + # class MyContext < GraphQL::Query::Context; end + # + # context_class MyContext + # + # # ... + # end + # ~~~ + # + # this compiler will produce the RBI file `my_schema.rbi` with the following content: + # + # ~~~rbi + # # my_schema.rbi + # # typed: true + # class MySchema + # sig { returns(MySchema::MyContext) } + # def context; end + # end + # ~~~ + class GraphqlSchema < Compiler + extend T::Sig + + ConstantType = type_member { { fixed: T.class_of(GraphQL::Schema) } } + + sig { override.void } + def decorate + custom_context_class = constant.context_class + # Skip decoration if the context class hasn't been customized + return if custom_context_class == GraphQL::Query::Context + + return if constant.method_defined?(:context, false) # Skip if the Schema overrides the `#context` getter. + + root.create_path(constant) do |schema| + schema.create_method("context", return_type: T.must(name_of(custom_context_class))) + end + end + + class << self + extend T::Sig + + sig { override.returns(T::Enumerable[Module]) } + def gather_constants + all_classes.select { |c| c < GraphQL::Schema && c != GraphQL::Query::NullContext::NullSchema } + end + end + end + end + end +end diff --git a/manual/compiler_graphqlschema.md b/manual/compiler_graphqlschema.md new file mode 100644 index 000000000..46c970bcd --- /dev/null +++ b/manual/compiler_graphqlschema.md @@ -0,0 +1,27 @@ +## GraphqlSchema + +`Tapioca::Dsl::Compilers::GraphqlSchema` generates RBI files for subclasses of +[`GraphQL::Schema`](https://graphql-ruby.org/api-doc/2.1.7/GraphQL/Schema). + +For example, with the following `GraphQL::Schema` subclass: + +~~~rb +class MySchema> < GraphQL::Schema + class MyContext < GraphQL::Query::Context; end + + context_class MyContext + + # ... +end +~~~ + +this compiler will produce the RBI file `my_schema.rbi` with the following content: + +~~~rbi +# my_schema.rbi +# typed: true +class MySchema + sig { returns(MySchema::MyContext) } + def context; end +end +~~~ diff --git a/manual/compilers.md b/manual/compilers.md index ebeca18ed..391832a6e 100644 --- a/manual/compilers.md +++ b/manual/compilers.md @@ -28,6 +28,7 @@ In the following section you will find all available DSL compilers: * [FrozenRecord](compiler_frozenrecord.md) * [GraphqlInputObject](compiler_graphqlinputobject.md) * [GraphqlMutation](compiler_graphqlmutation.md) +* [GraphqlSchema](compiler_graphqlschema.md) * [IdentityCache](compiler_identitycache.md) * [JsonApiClientResource](compiler_jsonapiclientresource.md) * [Kredis](compiler_kredis.md) diff --git a/spec/tapioca/dsl/compilers/graphql_schema_spec.rb b/spec/tapioca/dsl/compilers/graphql_schema_spec.rb new file mode 100644 index 000000000..9db35d49e --- /dev/null +++ b/spec/tapioca/dsl/compilers/graphql_schema_spec.rb @@ -0,0 +1,103 @@ +# typed: strict +# frozen_string_literal: true + +require "spec_helper" + +module Tapioca + module Dsl + module Compilers + class GraphqlSchemaSpec < ::DslSpec + describe "Tapioca::Dsl::Compilers::GraphqlSchema" do + describe "initialize" do + it "gathers no constants if there are no GraphQL::Schema subclasses" do + assert_empty(gathered_constants) + end + + it "gathers only GraphQL::Schema subclasses" do + add_ruby_file("content.rb", <<~RUBY) + class MySchema < GraphQL::Schema + end + + class User + end + RUBY + + assert_equal(["MySchema"], gathered_constants) + end + + it "gathers subclasses of GraphQL::Schema subclasses" do + add_ruby_file("content.rb", <<~RUBY) + class MyBaseSchema < GraphQL::Schema + end + + class MySchema < MyBaseSchema + end + RUBY + + assert_equal(["MyBaseSchema", "MySchema"], gathered_constants) + end + end + + describe "decorate" do + it "generates an empty RBI file if there is no custom context_class is set" do + add_ruby_file("create_comment.rb", <<~RUBY) + class MySchema < GraphQL::Schema + end + RUBY + + expected = <<~RBI + # typed: strong + RBI + + assert_equal(expected, rbi_for(:MySchema)) + end + + it "generates correct RBI file for subclass that sets context_class" do + add_ruby_file("create_comment.rb", <<~RUBY) + class MySchema < GraphQL::Schema + class MyContext < GraphQL::Query::Context; end + + context_class MyContext + end + RUBY + + expected = <<~RBI + # typed: strong + + class MySchema + sig { returns(MySchema::MyContext) } + def context; end + end + RBI + + assert_equal(expected, rbi_for(:MySchema)) + end + + it "generates an empty RBI file if there is an inline signature" do + add_ruby_file("create_comment.rb", <<~RUBY) + class MySchema < GraphQL::Schema + extend T::Sig + + class MyContext < GraphQL::Query::Context; end + + context_class MyContext + + sig { returns(SomethingElse) } + def context + # ... + end + end + RUBY + + expected = <<~RBI + # typed: strong + RBI + + assert_equal(expected, rbi_for(:MySchema)) + end + end + end + end + end + end +end