class

Analyzer::CSharp::HotChocolate

Inherits Analyzer::CSharp::Common < Analyzer < FileHelper < Reference < Object

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 ::String
SCALARS = {"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/

Class methods

tech_name
Source

Instance methods

analyze
Source
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

Nested types