test(strawberry-shake): characterize shared-fragment model naming gap (#6873)#10
Open
cliedeman wants to merge 1 commit into
Open
test(strawberry-shake): characterize shared-fragment model naming gap (#6873)#10cliedeman wants to merge 1 commit into
cliedeman wants to merge 1 commit into
Conversation
Add a dedicated, cross-framework characterizing test for ChilliCream#6873. When the same named fragment is spread across two operations, the generator emits two divergently named per-operation data classes (e.g. GetStudentById_Student_Student and GetStudents_Students_Student) instead of one shared model named from the fragment. The fragment INTERFACES (IMyStudent, IMySchool) are already shared, but the data classes are not, and a nested property such as the student's school is typed by an operation-scoped interface (IGetStudentById_Student_School) rather than the fragment interface (IMySchool). The test asserts on the generated source from CSharpGenerator.Generate(...).Documents[].SourceText and documents the current behaviour so a future fix has a clear target. No production code is changed: a complete fix is large and invasive (no cross-operation model registry exists today and unifying the data classes would touch the JSON deserializers, entity mappers, store wiring and ~86 snapshot files), so it is intentionally out of scope for this PR.
10 tasks
cliedeman
force-pushed
the
fix/ss-shared-fragment-model-naming
branch
from
June 27, 2026 14:34
fc59775 to
06e18cc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relates to ChilliCream#6873
Problem
When the same named GraphQL fragment is spread across two operations, Strawberry Shake's code generator produces two separate, divergently named per-operation models instead of one shared model named from the fragment.
For the schema/operations in ChilliCream#6873 (a
MyStudentfragment spread by bothGetStudentByIdandGetStudents, whereMyStudentitself spreadsMySchool), the generator emits today:GetStudentByIdGetStudentsGetStudentById_Student_StudentGetStudents_Students_StudentIGetStudentById_Student_StudentIGetStudents_Students_StudentGetStudentById_Student_School_SchoolGetStudents_Students_School_SchoolIGetStudentById_Student_SchoolIGetStudents_Students_SchoolThe shared fragment interfaces
IMyStudentandIMySchoolare generated (one each), but:MyStudent/MySchoolclass), andschoolproperty is typed by an operation-scoped interface (IGetStudentById_Student_School) rather than the nested fragment interface (IMySchool), which is the "randomly assigned type" symptom reported in the issue.These names are derived from the operation name plus the field path, so they change when operations/fields are added or reordered, which is why several users in the issue report build pipelines that "randomly" fail across machines.
Root cause (analysis)
InterfaceTypeSelectionSetAnalyzer.AnalyzeWithDefaults/AnalyzeOperationbuild each per-operation interface and class viaFragmentHelper.CreateInterface(...)/CreateClass(...), passingfieldSelection.Path(operation name + field path). (src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/InterfaceTypeSelectionSetAnalyzer.cs)FragmentHelper.CreateFragmentNode(...)callsCreateName(path, GetClassName), which joins the path segments with_, so the names are inherently operation-scoped. (.../Analyzers/FragmentHelper.cs)fragmentNode.Fragment.Name(the GraphQL fragment name) and cached by name, which is why only they are shared. (.../Analyzers/FragmentHelper.cs,CreateInterface)DocumentAnalyzer.Analyze()creates a freshDocumentAnalyzerContext(and therefore a fresh model cache) per operation. The named-fragment identity is also lost duringRewriteForConcreteType, so the data class has no fragment name to fall back to.What this PR does
SharedFragmentModelNamingTests, a dedicated characterizing test class (insrc/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/) that asserts on the generated source (CSharpGenerator.Generate(...).Documents[].SourceText) and locks in the current behaviour:MyStudentclass exists;MySchoolclass exists;IMyStudentand oneIMySchoolinterface are generated;schoolproperty is typed by the per-operation interface, notIMySchool.These tests are written so that the
Assert.False/Assert.Equal(0, ...)expectations flip when the shared-fragment behaviour is eventually fixed, giving a future change a clear target.What this PR does NOT do (remaining scope)
It does not fix the divergence. A complete fix (one shared, fragment-named data class reused across operations, with nested properties typed by the fragment interface) is large and invasive:
DocumentAnalyzerContextand its model cache are per-operation), so a shared registry plus a structural-merge/equivalence guarantee would have to be introduced;TypeDescriptorMapperdepend on it);Given the fork's "minimal, focused, safe PR" guidance and the risk of destabilizing the generator or mass-updating snapshots, the unification is intentionally deferred to a separate, explicitly budgeted effort. A lower-risk partial step identified during analysis (typing nested properties by the fragment interface in
TypeDescriptorMapper.GetFieldTypeDescriptor) is also out of scope here because it would still churn many snapshots.— Claude
🤖 Generated with Claude Code