Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-t4": {
"version": "3.0.0",
"commands": [
"t4"
],
"rollForward": false
}
}
}
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
cache: true
cache-dependency-path: '**/packages.lock.json'

- name: Restore tools
run: dotnet tool restore

- name: Restore
run: dotnet restore Linq2GraphQL.CI.slnf --locked-mode

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
cache: true
cache-dependency-path: '**/packages.lock.json'

- name: Restore tools
run: dotnet tool restore

- name: Publish
working-directory: docs/Linq2GraphQL.Docs
run: dotnet publish -c:Release -o:publish
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
id: nbgv
- run: echo 'SemVer2=${{ steps.nbgv.outputs.SemVer2 }}'

- name: Restore tools
run: dotnet tool restore

- name: Restore
run: dotnet restore Linq2GraphQL.Release.slnf --locked-mode

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,6 @@ FodyWeavers.xsd
.build-timestamp
config.json
local-nuget/

# Preprocessed T4 templates (generated at build time by dotnet-t4)
src/Linq2GraphQL.Generator/Templates/**/*.g.cs
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Key details that bite:

`ClientGenerator.GenerateAsync` posts an introspection query (`General.IntrospectionQuery`, or the `IncludeDeprecated` variant), deserializes into `GraphQLSchema/RootSchema`, and drives one T4 template per output kind (`Templates/{Client,Class,Interface,Methods,Enum,Scalars}`). Templates return `FileEntry` objects; `Program.cs` writes them with `ReplaceLineEndings("\n")` — generated files are always LF, keep it that way.

**T4 workflow (from DEVELOPER.md — read it before touching templates):** each template is three files — `X.tt` (source, edit this), `X.tt.cs` (hand-written partial with constructor params and helpers), and `X.cs` (preprocessed output, **checked in**). Editing a `.tt` does nothing until the `.cs` is regenerated via Visual Studio's *Run Custom Tool* on the `.tt` file. There is no CLI equivalent wired up, so a template change made outside Visual Studio will silently build and run the old logic.
**T4 workflow (from DEVELOPER.md — read it before touching templates):** each template is three files — `X.tt` (source, edit this), `X.tt.cs` (hand-written partial with constructor params and helpers), and `X.g.cs` (preprocessed output, **generated at build time, gitignored**). The generator's csproj runs the pinned `dotnet-t4` local tool (`.config/dotnet-tools.json`) over every `Templates\**\*.tt` before compiling, so editing a `.tt` and building is the whole loop and stale template logic cannot be built. Compile errors inside template code are reported against the `.tt` file and line. A template in a new folder needs a `T4Template` item with a `TemplateNamespace` in the csproj.

`GeneratorSettings.Current.Nullable` is ambient static state read from inside templates; the nullable and non-nullable clients differ mainly in nullable annotations and `#pragma warning disable CS8618`.

Expand Down
100 changes: 65 additions & 35 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ This document provides comprehensive guidance for developers working on the Linq

## Prerequisites

- **Visual Studio 2022** (recommended) or Visual Studio 2019/2022
- **.NET 8.0 SDK** or later
- **T4 Template Support** - Ensure the "Text Template Transformation" workload is installed in Visual Studio
- **.NET 10.0 SDK** or later
- **Any editor** - Visual Studio, Rider, VS Code or plain `dotnet build` all work;
T4 preprocessing runs as part of the build, so no IDE-specific tooling is required
- **`dotnet tool restore`** once per clone, to fetch the pinned `dotnet-t4` CLI tool
(the build does this for you)

## Project Structure

Expand All @@ -27,7 +29,8 @@ src/
│ │ ├── Class/ # Class generation templates
│ │ ├── Interface/ # Interface generation templates
│ │ ├── Methods/ # Method generation templates
│ │ └── Enum/ # Enum generation templates
│ │ ├── Enum/ # Enum generation templates
│ │ └── Scalars/ # Custom scalar templates
│ ├── GraphQLSchema/ # Schema parsing and processing
│ └── ClientGenerator.cs # Main generation orchestration
└── Linq2GraphQL.Client/ # Core client library
Expand All @@ -43,7 +46,8 @@ This project uses **T4 (Text Template Transformation Toolkit)** for code generat

- **`.tt`** - Source T4 template files (human-editable)
- **`.tt.cs`** - Partial class definitions for template variables and helper methods
- **`.cs`** - Preprocessed T4 templates (auto-generated, contains the actual `TransformText()` method)
- **`.g.cs`** - Preprocessed T4 templates (contains the actual `TransformText()` method).
**Generated at build time, gitignored, never checked in.**

### Template Development Workflow

Expand All @@ -52,35 +56,52 @@ This project uses **T4 (Text Template Transformation Toolkit)** for code generat
When modifying `.tt` files:

1. **Edit the `.tt` file** with your changes
2. **Manually regenerate the `.cs` file** using Visual Studio's custom tool
3. **Build the project** to ensure compilation
4. **Test the generation** by running the client generator
2. **Build the project** - the `.g.cs` file is regenerated automatically
3. **Test the generation** by running the client generator

#### 2. Manual Template Regeneration
That is the whole loop. There is no manual regeneration step, and no way to build
stale template logic: compile errors in a template point straight back at the
`.tt` file and line number.

**⚠️ IMPORTANT: After modifying any `.tt` file, you MUST manually regenerate the corresponding `.cs` file.**
#### 2. How Build-Time Preprocessing Works

**In Visual Studio 2022:**
`Linq2GraphQL.Generator.csproj` preprocesses every `Templates\**\*.tt` into a
sibling `<Template>.g.cs` before compiling, using the
[dotnet-t4](https://www.nuget.org/packages/dotnet-t4) CLI tool pinned in
`.config/dotnet-tools.json`:

1. Right-click on the `.tt` file in Solution Explorer
2. Select **"Run Custom Tool"**
3. This will regenerate the `.cs` file with your changes
4. Verify the `.cs` file contains your updated template logic
| Target | Does |
| --- | --- |
| `RestoreT4Tool` | Runs `dotnet tool restore` (once per project file change) |
| `PreprocessT4Templates` | Runs `dotnet t4 --class=<ns>.<Name> --out=<Name>.g.cs <Name>.tt` per template, incrementally |
| `IncludeT4Output` | Adds the `.g.cs` files to `Compile` before `BeforeCompile` |

**Alternative method:**
1. Right-click on the `.tt` file
2. Select **"Properties"**
3. Set **"Custom Tool"** to `TextTemplatingFilePreprocessor`
4. Set **"Custom Tool Namespace"** to your desired namespace
5. Save the file to trigger regeneration
New template folders need one line in the `T4Template` item group so the class
namespace can be derived:

```xml
<T4Template Include="Templates\MyFolder\*.tt" TemplateNamespace="Templates.MyFolder"/>
```

To preprocess a single template by hand (rarely needed - the build does it):

```powershell
dotnet tool restore
cd src/Linq2GraphQL.Generator
dotnet t4 --class="Linq2GraphQL.Generator.Templates.Enum.EnumTemplate" --out="Templates/Enum/EnumTemplate.g.cs" "Templates/Enum/EnumTemplate.tt"
```

Visual Studio's *Run Custom Tool* is **no longer used** and the `.tt` files
deliberately carry no `Generator`/`LastGenOutput` metadata - the build owns
generation on every platform, IDE or CI.

#### 3. Template File Dependencies

Each T4 template requires:

- **`.tt` file** - Contains the template logic and output format
- **`.tt.cs` file** - Provides the partial class with constructor parameters and helper methods
- **`.cs` file** - Auto-generated preprocessed template (regenerated from `.tt`)
- **`.g.cs` file** - Preprocessed template, produced by the build (never edited or committed)

### Template Syntax

Expand Down Expand Up @@ -130,7 +151,7 @@ public partial class TemplateName
### 1. Development Cycle

```
Edit .tt file → Run Custom Tool → Build Project → Test Generation → Repeat
Edit .tt file → Build Project → Test Generation → Repeat
```

### 2. Testing Changes
Expand Down Expand Up @@ -161,19 +182,26 @@ dotnet run --project src/Linq2GraphQL.Generator -- <endpoint> [options]
**Problem:** Changes to `.tt` files not reflected in generated output.

**Solution:**
1. Ensure you've run the **"Run Custom Tool"** on the `.tt` file
2. Check that the `.cs` file was updated with your changes
3. Clean and rebuild the project
4. Verify the T4 preprocessor is working in Visual Studio
1. Confirm the template folder has a `T4Template` entry in
`Linq2GraphQL.Generator.csproj` - a template outside those globs is never preprocessed
2. Check the timestamp of the sibling `.g.cs` file; delete it and rebuild to force regeneration
3. Run `dotnet build src/Linq2GraphQL.Generator -v:n` and look for the `dotnet t4` command lines

#### `dotnet t4` Not Found

**Problem:** Build fails with "Cannot find command 'dotnet t4'".

**Solution:** Run `dotnet tool restore` from the repository root - the tool is a local
tool pinned in `.config/dotnet-tools.json`. The build normally does this for you.

#### Missing TransformText Method

**Problem:** Compilation error "does not contain a definition for 'TransformText'".

**Solution:**
1. The `.cs` file is missing or outdated
2. Run **"Run Custom Tool"** on the corresponding `.tt` file
3. Ensure the `.tt.cs` file exists and has the correct partial class definition
1. The `.g.cs` file was not produced - see *T4 Templates Not Regenerating* above
2. Ensure the `.tt.cs` file exists, and that its namespace and class name match the
`--class` value the build derives (`$(RootNamespace).<TemplateNamespace>.<Filename>`)

#### Template Variables Not Available

Expand All @@ -186,10 +214,12 @@ dotnet run --project src/Linq2GraphQL.Generator -- <endpoint> [options]

### Debugging Tips

1. **Check the `.cs` file content** - It should contain your template logic in the `TransformText()` method
2. **Verify template compilation** - Build errors often indicate template syntax issues
3. **Use Visual Studio's T4 debugging** - Set breakpoints in the generated `.cs` files
4. **Check build output** - Look for T4-related error messages
1. **Read the compile error location** - errors inside template code are reported against
the `.tt` file and line, thanks to the `#line` pragmas in the generated `.g.cs`
2. **Inspect the `.g.cs` file** - it sits next to the `.tt` and contains the generated
`TransformText()` method
3. **Set breakpoints in the `.g.cs` file** to step through template execution
4. **Check build output** - `dotnet t4` failures surface as `Exec` task errors

## Best Practices

Expand Down Expand Up @@ -217,7 +247,7 @@ dotnet run --project src/Linq2GraphQL.Generator -- <endpoint> [options]
### Version Control

1. **Commit `.tt` and `.tt.cs` files** - These are source files
2. **Ignore generated `.cs` files** - Add `**/*.cs` to `.gitignore` for auto-generated files
2. **Never commit `.g.cs` files** - they are build output and are gitignored
3. **Document template changes** - Include clear commit messages for template modifications
4. **Review generated output** - Verify that template changes produce the expected results

Expand Down
4 changes: 2 additions & 2 deletions REPO_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Three stages, and understanding them is usually the whole job:

## 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.
- ~~**T4 is the sharpest edge in the repo.**~~ **Fixed.** Templates are now preprocessed at build time by the pinned `dotnet-t4` local tool into gitignored `X.g.cs` files, so a `.tt` edit takes effect on the next `dotnet build` on any platform and stale template logic cannot be built. (Switching it on revealed one already-dormant edit: `ScalarTemplate.tt` referenced a non-existent `GraphqlType.ScalarTypeName` and had never been regenerated.)
- **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.
Expand All @@ -69,7 +69,7 @@ Three stages, and understanding them is usually the whole job:

## 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.
1. ~~Wire up **CLI T4 regeneration**~~ — done: `dotnet-t4` runs from the generator's csproj on every build, and the preprocessed output is no longer checked in.
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`.
Expand Down
Loading
Loading