Skip to content

[Analyzer] Support IsSelected attribute#9527

Merged
tobias-tengler merged 2 commits intomainfrom
tte/support-isselected-in-source-generator
Apr 10, 2026
Merged

[Analyzer] Support IsSelected attribute#9527
tobias-tengler merged 2 commits intomainfrom
tte/support-isselected-in-source-generator

Conversation

@tobias-tengler
Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings April 10, 2026 15:09
@tobias-tengler tobias-tengler merged commit 404d7e9 into main Apr 10, 2026
130 of 132 checks passed
@tobias-tengler tobias-tengler deleted the tte/support-isselected-in-source-generator branch April 10, 2026 15:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the HotChocolate Types.Analyzers source generator to recognize resolver parameters annotated with [IsSelected] and generate optimized selection-check code (single field, multiple fields, and selection patterns), with accompanying snapshot and integration test coverage.

Changes:

  • Add ResolverParameterKind.IsSelected and detection via IsSelectedAttribute.
  • Generate resolver argument binding code for [IsSelected], including precomputed field sets / parsed selection sets when beneficial.
  • Add analyzer snapshot tests and new integration tests validating runtime behavior (including node resolvers) and update schema snapshot.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs Adds missing metadata references needed to compile generated code that uses parser/visitor types.
src/HotChocolate/Core/test/Types.Analyzers.Tests/ResolverTests.cs Adds snapshot tests for [IsSelected] scenarios (single/multi/pattern, node resolver).
src/HotChocolate/Core/test/Types.Analyzers.Tests/snapshots/ResolverTests.GenerateSource_ResolverWithIsSelectedSingleField_MatchesSnapshot.md New golden snapshot for single-field [IsSelected].
src/HotChocolate/Core/test/Types.Analyzers.Tests/snapshots/ResolverTests.GenerateSource_ResolverWithIsSelectedMultipleFields_MatchesSnapshot.md New golden snapshot for multi-field [IsSelected] (field-set optimization).
src/HotChocolate/Core/test/Types.Analyzers.Tests/snapshots/ResolverTests.GenerateSource_ResolverWithIsSelectedPattern_MatchesSnapshot.md New golden snapshot for pattern-based [IsSelected] (parsed selection set + visitor).
src/HotChocolate/Core/test/Types.Analyzers.Tests/snapshots/ResolverTests.GenerateSource_NodeResolverWithIsSelectedPattern_MatchesSnapshot.md New golden snapshot covering node resolver + pattern-based [IsSelected].
src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs Adds query + node type used to validate [IsSelected] at runtime.
src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/IsSelectedTests.cs Adds integration tests validating true/false outcomes for field selection and node resolution.
src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/snapshots/IntegrationTests.Schema_Snapshot.snap Updates schema snapshot to include the new isSelectedTest field and IsSelectedNode type.
src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs Adds IsSelectedAttribute to well-known attribute constants.
src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverParameterKind.cs Introduces IsSelected parameter kind.
src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverParameter.cs Treats IsSelected as “pure” (no binding required).
src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs Adds attribute-based detection for [IsSelected].
src/HotChocolate/Core/src/Types.Analyzers/Helpers/CompilationExtensions.cs Maps [IsSelected] parameters to the new ResolverParameterKind.IsSelected.
src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs Core generator updates: emits argument generation + optional precomputed fields/selection parsing for [IsSelected].
src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeFileBuilder.cs Ensures node resolver paths also emit/init [IsSelected] helpers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

Writer.WriteIndentedLine(
"_isSelected_{0}_{1} = new global::System.Collections.Generic.HashSet<string>([{2}]);",
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated HashSet initialization uses a collection expression ([...]), which requires C# 12. Source generator output should generally compile under the consumer project's language version; using new[] { ... } / new string[] { ... } here would avoid introducing a hard C# 12 dependency.

Suggested change
"_isSelected_{0}_{1} = new global::System.Collections.Generic.HashSet<string>([{2}]);",
"_isSelected_{0}_{1} = new global::System.Collections.Generic.HashSet<string>(new string[] {{ {2} }});",

Copilot uses AI. Check for mistakes.
Comment on lines +2105 to +2106
Writer.WriteIndentedLine(
$"_isSelected_{resolver.Member.Name}_{parameter.Name} = global::HotChocolate.Language.Utf8GraphQLParser.Syntax.ParseSelectionSet(\"{{ {patternString} }}\");");
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patternString is emitted directly into a C# string literal for ParseSelectionSet(...) without escaping. If the pattern includes quotes/backslashes/newlines (e.g. field arguments or directives with string values), the generated source will not compile; please escape the value (you already have EscapeString(...) in this file).

Suggested change
Writer.WriteIndentedLine(
$"_isSelected_{resolver.Member.Name}_{parameter.Name} = global::HotChocolate.Language.Utf8GraphQLParser.Syntax.ParseSelectionSet(\"{{ {patternString} }}\");");
var selectionSet = EscapeString($"{{ {patternString} }}");
Writer.WriteIndentedLine(
$"_isSelected_{resolver.Member.Name}_{parameter.Name} = global::HotChocolate.Language.Utf8GraphQLParser.Syntax.ParseSelectionSet(\"{selectionSet}\");");

Copilot uses AI. Check for mistakes.
Comment on lines +2168 to +2179
// Single string constructor
if (args.Length == 1)
{
var fieldName = (string)args[0].Value!;

if (fieldName.IndexOf(' ') < 0 && fieldName.IndexOf('{') < 0)
{
return (IsSelectedVariant.SingleField, [fieldName], null);
}

return (IsSelectedVariant.Pattern, [], fieldName);
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SingleField vs Pattern detection for the single-string ctor argument only checks for spaces / {. A value like name(arg: "x"), name@skip(if:true), or other non-GraphQL-Name characters would be treated as SingleField and generate IsSelected("...") (likely always false) instead of being parsed as a selection set. Consider using a proper GraphQL Name check (or parsing) to decide the variant.

Copilot uses AI. Check for mistakes.
Comment on lines +1717 to +1770
case ResolverParameterKind.IsSelected:
var (variant, fieldNames, _) = GetIsSelectedInfo(parameter);

switch (variant)
{
case IsSelectedVariant.SingleField:
Writer.WriteIndentedLine(
"var args{0} = context.Select().IsSelected(\"{1}\");",
i,
fieldNames[0]);
break;

case IsSelectedVariant.MultipleFields:
var sb = new StringBuilder();
for (var j = 0; j < fieldNames.Length; j++)
{
if (j > 0)
{
sb.Append(", ");
}

sb.Append('"');
sb.Append(fieldNames[j]);
sb.Append('"');
}

Writer.WriteIndentedLine(
"var args{0} = context.Select().IsSelected({1});",
i,
sb.ToString());
break;

case IsSelectedVariant.FieldSet:
Writer.WriteIndentedLine(
"var args{0} = context.Select().IsSelected(_isSelected_{1}_{2});",
i,
resolver.Member.Name,
parameter.Name);
break;

case IsSelectedVariant.Pattern:
Writer.WriteIndentedLine(
"var args{0}_selectionContext = new global::HotChocolate.Resolvers.IsSelectedContext(context.Schema, context.Select());",
i);
Writer.WriteIndentedLine(
"global::HotChocolate.Resolvers.IsSelectedVisitor.Instance.Visit(_isSelected_{0}_{1}, args{2}_selectionContext);",
resolver.Member.Name,
parameter.Name,
i);
Writer.WriteIndentedLine(
"var args{0} = args{0}_selectionContext.AllSelected;",
i);
break;
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResolverParameterKind.IsSelected is handled here for non-batch resolvers, but the batch-resolver argument generation switch (in WriteBatchResolver) does not handle IsSelected and will fall back to default(...), causing batch resolvers to always receive false (or the default) for [IsSelected] parameters. Add corresponding handling in the batch-resolver path (likely mirroring the Selection case using contexts[0]).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants