From 7e39d31e241b05622b19b6714e56d315637452be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Dang=C3=A5rden?= Date: Mon, 7 Sep 2026 18:57:08 +0200 Subject: [PATCH] Add high-level repo analysis report Documents the three shipped packages, the query pipeline, line-count proportions, strengths, risks and suggested priorities, as reviewed at main @ a17c22b. Co-Authored-By: Claude Opus 5 (1M context) --- REPO_REPORT.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 REPO_REPORT.md diff --git a/REPO_REPORT.md b/REPO_REPORT.md new file mode 100644 index 00000000..70d9407b --- /dev/null +++ b/REPO_REPORT.md @@ -0,0 +1,88 @@ +# Linq2GraphQL.Client — High-Level Repo Report + +*Analysed at `main` @ `a17c22b` (2026-09-07)* + +## 1. What the repo is + +A **LINQ-to-GraphQL client for .NET**, shipped as three NuGet artifacts: + +| Artifact | Kind | Role | +| --- | --- | --- | +| `Linq2GraphQL.Generator` | `dotnet tool` (`Linq2GraphQL`) | Introspects a GraphQL endpoint, emits a strongly-typed C# client via T4 templates | +| `Linq2GraphQL.Client` | library | Runtime: turns `Include`/`Select` lambdas into a query tree, executes, deserializes | +| `Linq2GraphQL.Client.Subscriptions` | library | Optional subscriptions transport (graphql-ws + SSE) | + +Everything else in the tree — `test/`, `docs/`, `StartGG/` — exists to exercise or demonstrate those three. + +## 2. Size and shape + +Hand-written C# (excluding `bin`/`obj`): + +``` +src/Linq2GraphQL.Generator 6 701 lines (32 files) <- mostly T4 output +src/Linq2GraphQL.Client 2 584 lines (45 files) +src/Linq2GraphQL.Client.Subscriptions 383 lines ( 9 files) +test/Linq2GraphQL.Tests 2 190 lines (19 files, 126 [Fact]/[Theory]) +test/Linq2GraphQL.TestClient* 6 523 lines (checked-in generated output) +docs/ + StartGG/ 15 465 lines (Blazor site + sample clients) +``` + +**The product itself is small — under 3 000 lines of runtime code.** That is the headline: a compact, focused library where the generator's line count is inflated by machine-generated template partials, not by complexity. + +Largest runtime files: `Converters/EnumConverter.cs` (343), `Visitors/QueryExpressionVisitor.cs` (332), `QueryNode.cs` (244), `Utilities.cs` (185). + +## 3. Architecture in one paragraph + +Three stages, and understanding them is usually the whole job: + +1. **Build** — generated `QueryMethods`/`MutationMethods` return `GraphQuery` seeded with a root `QueryNode` (field name + `ArgumentValue`s). +2. **Parse** — `Include(...)`/`Select(...)` lambdas go through `Utilities.ParseExpression` -> `QueryExpressionVisitor`. Two modes: `ResolvePath` for expressions that *name* a field (member chains, `[GraphQLMember]` methods, LINQ operators), and plain `ExpressionVisitor` walking for everything else so every mentioned field still lands in the query. Lambda parameters bind to nodes keyed **by `ParameterExpression` reference**, so nested lambdas reusing a name stay distinct. +3. **Execute** — `GraphBaseExecute` lazily assigns unique variable names and auto-adds primitive children, renders query text from the `QueryNode` tree; `QueryExecutor` POSTs and unwraps `data`/`errors`/`extensions`. + +`QueryNode` is the entire intermediate representation. Everything interesting happens between it and the visitor. + +## 4. Highlights — the good + +- **The test strategy is unusually strong for a library this size.** 126 tests, and most are genuinely end-to-end: `WebApplicationFactory` boots a real HotChocolate server in-process (`Linq2GraphQL.TestServer`) and the *checked-in generated client* queries it. A passing suite proves the generator, the parser, the query text and the deserializer all agree — not just that a unit returns the expected string. +- **The nullable variant is a first-class citizen**, not an afterthought: a parallel `TestServerNullable` + `TestClientNullable` + fixture pair. +- **Build hygiene is modern and tight.** .NET 10 throughout, central package management (`Directory.Packages.props`), `RestorePackagesWithLockFile` with `--locked-mode` in CI, Nerdbank.GitVersioning driving versions from git, and solution filters splitting CI (`Linq2GraphQL.CI.slnf`) from pack/publish (`Linq2GraphQL.Release.slnf`). No hand-edited version numbers anywhere. +- **Release is one button.** `release.yml` is `workflow_dispatch`-only: nbgv -> pack -> push to NuGet -> GitHub release. Low ceremony, hard to trigger by accident. +- **Deliberate design details that show maturity:** argument-hash-derived GraphQL aliases so the same field can be requested twice with different arguments; opt-in "safe mode" that validates auto-included primitives against a cached introspection result; two error surfaces (`ExecuteAsync` throws, `ExecuteWithResultAsync` returns `GraphResult`) funnelled through a single `ProcessResponseFull`. +- **Generated files are normalised to LF** (`ReplaceLineEndings("\n")` in `Program.cs`) — a small thing that prevents a lot of cross-platform diff noise. + +## 5. Highlights — the risks + +- **T4 is the sharpest edge in the repo.** Each template is three files: `X.tt` (source), `X.tt.cs` (hand-written partial), `X.cs` (**preprocessed output, checked in**). Regeneration happens only via Visual Studio's *Run Custom Tool*; there is no CLI equivalent wired up. A template edit made outside VS **builds and runs the old logic silently**. This is the single most likely way to lose an afternoon here. +- **Generated test clients are checked in but never regenerated by the build.** `TestClient`/`TestClientNullable` must be re-generated by hand against a locally running TestServer after any template or schema change. Nothing in CI detects the drift — the tests keep passing against stale output. +- **Coupling between alias hashing and deserialization.** `Utilities.GetArgumentsId` must produce the *same* hash at write time and read time. Any change to argument hashing breaks reads as well as writes, and the failure mode is a silently missing field rather than an exception. +- **Ambient static generator state.** `GeneratorSettings.Current` is read from inside templates, so nullable/non-nullable output depends on global mutable state rather than a passed parameter. Fine today; awkward if generation ever needs to run concurrently. +- **`docs/` and `StartGG/` are outside the CI filter and can drift.** They are in `Linq2GraphQL.sln` but not `Linq2GraphQL.CI.slnf`, so nothing verifies they still compile — roughly 15k lines CI never touches. +- **Subscriptions are only partly covered.** Two transports exist (`WSClient` for graphql-ws, `SSEClient` for SSE), but only SSE works under the test host — and there is exactly **1** subscription test. The WebSocket path is effectively untested. +- **Minor tidy-ups:** `nuget.config.backup` is committed alongside `nuget.config`; a stray root-level `StarWars.Client/` containing only `obj/` shadows the real `docs/StarWars.Client`; the README options list has a few typos (`--nullabel`, "Exprimental"). + +## 6. Activity and ownership + +- **255 commits**, active since 2023: 110 (2023), 60 (2024), 73 (2025), 12 (2026 YTD). +- **Concentrated ownership.** Joakim Dangården holds 180 of 255 commits across two identities; Magnus Ahlberg 62; a long tail of 4 outside contributors with ~19 between them. Bus factor is effectively one. +- **Recent work is squarely in the core.** The last six months of churn lands on `src/Linq2GraphQL.Client` (10 touches, 5 of them in `Visitors/`), Subscriptions (7), and the tests (6). Notable recent commits: the **expression parser rewrite** (#92), the .NET 10 upgrade with CVE/warning cleanup (#90), and structured GraphQL error handling contributed externally (#88). +- **PR-driven workflow.** Nearly every change lands as a merge commit from a named branch — the history reads cleanly. + +## 7. If I were picking up work here + +1. Wire up **CLI T4 regeneration** (or a CI check that regenerating produces no diff). This removes the repo's one silent-failure trap. +2. Add a **CI job that regenerates the test clients and diffs them** — it closes the stale-generated-output gap for free. +3. Pull `docs/` and `StartGG/` into a **build-only CI job** so they cannot rot unnoticed. +4. Get the **WebSocket subscription transport under test**, even against a standalone host outside `WebApplicationFactory`. +5. Delete `nuget.config.backup` and the stray root `StarWars.Client/`. + +## Appendix — commands that matter + +```powershell +# What CI runs +dotnet restore Linq2GraphQL.CI.slnf --locked-mode +dotnet build Linq2GraphQL.CI.slnf --no-restore +dotnet test Linq2GraphQL.CI.slnf --no-build + +# Generate a client against a live endpoint +dotnet run --project src/Linq2GraphQL.Generator -- -c=ClientName -n=Namespace -o=Generated +```