HotChocolate (https://chillicream.com/docs/hotchocolate) code-first
schemas: every root (Query/Mutation/Subscription) field becomes one
endpoint in the same shape the SDL analyzer emits.
A class is a root when it carries [QueryType]/[MutationType]/
[SubscriptionType] (source generator; several classes merge into one
root), [ExtendObjectType(...)] naming a root, or is registered with
AddQueryType<T>() and friends. A registered ObjectType<T> descriptor
class contributes T's members plus its literal descriptor.Field("x")
fields. Wiring (Program.cs) and root classes live in different files,
so the analyzer collects across every .cs file first, then resolves.
Field/argument names follow HotChocolate's NameFormattingHelpers:
[GraphQLName] wins, otherwise a method loses its Get prefix (and its
Async suffix when it returns a task/stream) and the leading uppercase
run is lowered.
Constants
ADD_ROOT = /\b(?:Add|Register)(Query|Mutation|Subscription)Type\s*<\s*([\w.]+)\s*>/
AddQueryType<Query>(), .AddMutationType<Mutations.Mutation>(), and the
pre-v11 RegisterQueryType<QueryType>().
ATTRIBUTE = /\[(?:[^\[\]"]|"[^"]*")+\]/
CLASS_DECL = /\bclass\s+(\w+)(?:\s*<[^>]*>)?\s*(?::\s*([^{]*))?/
CLASS_GATE = /\b(?:Query|Mutation|Subscription)Type\b|\bExtendObjectType\b|\bObjectType\s*<|\[\s*(?:Query|Mutation|Subscription)\s*[\](,]/
Files worth lexing for root classes, besides those naming a registered type.
EXTEND_ATTR = /\bExtendObjectType\s*(?:<\s*(?:[\w.]+\.)?(\w+)\s*>|\(\s*(?:Name\s*=\s*)?(?:typeof\s*\(\s*(?:[\w.]+\.)?(\w+)\s*\)|"(\w+)"|(?:[\w.]+\.)?OperationTypeNames\.(\w+)))/
[ExtendObjectType("Query")], (Name = "Query"), (typeof(Query)),
(OperationTypeNames.Query), [ExtendObjectType<Query>].
GLOBAL_ID = /\bAddGlobalObjectIdentification\s*\(/
Relay node(id:) / nodes(ids:) query fields.
GRAPHQL_NAME = /\bGraphQLName\s*\(\s*@?"([^"]+)"/
INJECTED_ATTR = /\b(?:Service|Parent|EventMessage|GlobalState|ScopedState|LocalState|IsSelected|FromServices|FromKeyedServices|ScopedService)\b/
Parameters HotChocolate resolves itself instead of exposing as
arguments (Resolvers/Expressions/Parameters/*ParameterExpressionBuilder).
Registered services are inferred without an attribute, so the shared
DI-type heuristic covers ApplicationDbContext, IBookService, ….
INJECTED_TYPES =
Set {"
CancellationToken", "
ClaimsPrincipal", "
DocumentNode", "
OperationDefinitionNode", "
Operation", "
Selection", "
Path", "
ConnectionFlags", "
ObjectType", "
ObjectField", "
Schema", "
SetState"}
LEADING_ATTRS = /\A\s*(?:\[(?:[^\[\]"]|"[^"]*")+\]\s*)+/
LIST_TYPES =
Set {"
IEnumerable", "
IQueryable", "
IAsyncEnumerable", "
IExecutable", "
List", "
IList", "
IReadOnlyList", "
ICollection", "
IReadOnlyCollection", "
HashSet", "
ISet", "
ImmutableArray", "
ImmutableList"}
MEMBER = /\A\s*public\s+((?:(?:static|async|virtual|override|sealed|new|partial|required|readonly|unsafe|extern)\s+)*)([\w<>\[\],.?\s]+?)\s+@?(\w+)\s*(\(|<|\{|=>)/
A public member: public static async Task<Book> GetBookAsync( or
public string Name { get; } / public string Hello => "x";.
MEMBER_ROOT_ATTR = /(?:\[|,)\s*(?:[\w.]+\.)?(Query|Mutation|Subscription)(?:Attribute)?\s*[\](,]/
HC 14+ source generator: [Query] public static Book GetBook() in any
static class adds that one member to the root.
MOUNT = /\b(?:MapGraphQL(?:Http)?|UseGraphQL)\s*\(\s*(?:path\s*:\s*)?@?"([^"]+)"/
app.MapGraphQL() / app.MapGraphQL("/api/graphql") (HC 11+), and the
older app.UseGraphQL("/graphql") middleware.
MUTATION_CON = /\bAddMutationConventions\s*\(([^)]*)\)/
NO_FIELD_RETURNS =
Set {"void", "
Task", "
ValueTask", "object", "
Task<object>", "
ValueTask<object>"}
NON_MEMBER_TYPES =
Set {"class", "record", "struct", "interface", "enum", "delegate", "event", "const"}
OBJECT_TYPE_OF = /\A\s*(?:[\w.]+\.)?ObjectType\s*(?:<\s*(?:[\w.]+\.)?(\w+)\s*>)?\s*(?:,|\z)/
ROOT_ATTR = /(?:\[|,)\s*(?:[\w.]+\.)?(Query|Mutation|Subscription)Type(?:Attribute)?\s*[\](,]/
[QueryType], [HotChocolate.Types.MutationType], [QueryType, Foo].
ROOT_KINDS = ["
Query", "
Mutation", "
Subscription"] of ::
StringSCALARS = {"string" => "
String", "
String" => "
String", "char" => "
String", "int" => "
Int", "
Int32" => "
Int", "short" => "
Short", "
Int16" => "
Short", "byte" => "
Byte", "long" => "
Long", "
Int64" => "
Long", "bool" => "
Boolean", "
Boolean" => "
Boolean", "float" => "
Float", "double" => "
Float", "
Single" => "
Float", "
Double" => "
Float", "decimal" => "
Decimal", "
Guid" => "
UUID", "
Uri" => "
URL", "
DateTime" => "
DateTime", "
DateTimeOffset" => "
DateTime", "
DateOnly" => "
Date", "
TimeSpan" => "
TimeSpan", "object" => "
Any"}
SKIP_MEMBER_ATTR = /\b(?:GraphQLIgnore|DataLoader)\b/
Members HotChocolate never binds (DefaultTypeInspector.CanBeHandled)
or that source generators claim ([DataLoader]).
SUBSCRIBE_WITH = /\bSubscribe\s*\(\s*With\s*=\s*(?:nameof\s*\(\s*(\w+)\s*\)|"(\w+)")/
[Subscribe(With = nameof(SubscribeToX))]: the stream method is not a field.
SYSTEM_MEMBERS =
Set {"
ToString", "
GetHashCode", "
Equals", "
CompareTo", "
Clone", "
GetType"}
WIRING_GATE = /\b(?:Add|Register)(?:Query|Mutation|Subscription)Type\b|\bMapGraphQL|\bUseGraphQL\b|\bAddMutationConventions\b|\bAddGlobalObjectIdentification\b/
Instance methods
tech
Instance-side view of the same declaration. The per-file rescues live on
this base class, which has no way to name the analyzer that is running
inside them, so a skipped file could not be attributed to a tech.
Deriving it from analyzer_for keeps the name written exactly once.
Source