|
1 | | -This document describes the development guidelines, project structure, and technical background for this repository. |
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
2 | 4 |
|
3 | 5 | ## Overview |
4 | 6 |
|
5 | | -This project is a C# source generator and analyzer suite that automatically generates Expression trees and DTO classes corresponding to IQueryable<T>.Select usages. It also provides code analyzers and fixes to improve LINQ query patterns and API design. |
| 7 | +Linqraft is a C# source generator and analyzer suite that automatically generates Expression trees and DTO classes for IQueryable<T>.Select operations. It enables writing LINQ queries with null-propagation operators (?.) in expression trees and auto-generates DTOs from anonymous types. |
6 | 8 |
|
7 | | -The source generator analyzes the contents of `.SelectExpr` calls and generates the corresponding `Select` expressions and DTO classes. |
| 9 | +The project consists of: |
| 10 | +- **Source Generator**: Analyzes `.SelectExpr` calls and generates Select expressions and DTO classes |
| 11 | +- **Interceptors**: Replaces SelectExpr calls with generated expressions at compile time |
| 12 | +- **Roslyn Analyzers**: Detects patterns and suggests code improvements with automatic fixes |
8 | 13 |
|
9 | | -Here is an example: |
| 14 | +## Build and Test Commands |
10 | 15 |
|
11 | | -```csharp |
12 | | -public class SampleClass |
13 | | -{ |
14 | | - public void GetSample(List<BaseClass> data) |
15 | | - { |
16 | | - var query = data.AsQueryable(); |
17 | | - // pattern 1: use anonymous type to specify selection |
18 | | - // return type is anonymous type |
19 | | - query.SelectExpr(x => new |
20 | | - { |
21 | | - x.Id, |
22 | | - // you can use the null-conditional operator |
23 | | - ChildDescription = x.Child?.Description, |
24 | | - }); |
25 | | - |
26 | | - // pattern 2: use an explicit DTO class |
27 | | - // return type is SampleDto (auto-generated) |
28 | | - query.SelectExpr<SampleDto>(x => new |
29 | | - { |
30 | | - x.Id, |
31 | | - // you can select child properties |
32 | | - ChildNames = x.Children.Select(c => c.Name).ToList(), |
33 | | - }); |
34 | | - |
35 | | - // pattern 3: use an already defined DTO class |
36 | | - query.SelectExpr(x => new PredefinedDto |
37 | | - { |
38 | | - x.Name, |
39 | | - x.Value, |
40 | | - }); |
41 | | - } |
42 | | -} |
43 | | - |
44 | | -public class PredefinedDto |
45 | | -{ |
46 | | - public string Name { get; set; } |
47 | | - public int Value { get; set; } |
48 | | -} |
| 16 | +### Clean build (required to avoid stale generator caches) |
| 17 | +```bash |
| 18 | +sh scripts/cleanup.sh |
| 19 | +dotnet build --no-incremental |
49 | 20 | ``` |
50 | 21 |
|
51 | | -## Project structure |
52 | | - |
53 | | -The repository is organized as follows (relevant folders under `src/`): |
54 | | - |
55 | | -### `src/Linqraft/` |
56 | | -The runtime library distributed as a NuGet package. Notable file: |
57 | | -- `DummyExpression.cs`: an empty extension method that acts as a marker for the Source Generator to detect `SelectExpr` usages. It performs no runtime work and exists only to be recognized at compile time. |
58 | | - |
59 | | -### `src/Linqraft.Core/` |
60 | | -Common infrastructure and helper classes shared between analyzers and source generators. This project contains: |
61 | | - |
62 | | -#### AnalyzerHelpers/ |
63 | | -Analyzer-specific helper classes and base infrastructure: |
64 | | -- `BaseLinqraftAnalyzer.cs`: Abstract base class for all analyzers, eliminating boilerplate code |
65 | | -- `CaptureHelper.cs`: Helper for detecting captured variables in lambda expressions |
66 | | -- `ExpressionHelper.cs`: Helper for extracting property names and member access chains from expressions |
67 | | -- `LinqMethodHelper.cs`: Helper for detecting LINQ method calls (Select, SelectMany, ToList) |
68 | | -- `NullCheckHelper.cs`: Helper for null checking patterns |
69 | | -- `SyntaxGenerationHelper.cs`: Helper for generating typed SelectExpr calls |
70 | | -- `SyntaxHelper.cs`: Helper for syntax node analysis and manipulation |
71 | | -- `UsingDirectiveHelper.cs`: Helper for managing using directives |
72 | | - |
73 | | -#### RoslynHelpers/ |
74 | | -Roslyn semantic analysis helpers: |
75 | | -- `RoslynTypeHelper.cs`: Type checking using Roslyn semantic analysis instead of string matching. Provides methods for nullable type checking, IQueryable/IEnumerable detection, anonymous type detection, and more. |
76 | | - |
77 | | -#### SyntaxHelpers/ |
78 | | -Syntax manipulation and formatting helpers: |
79 | | -- `NullConditionalHelper.cs`: Helper for null-conditional operator (?.) handling and null check pattern detection |
80 | | -- `TriviaHelper.cs`: Helper for preserving whitespace, comments, and formatting. Includes cross-platform line ending detection (CRLF vs LF). |
81 | | - |
82 | | -### `src/Linqraft.Analyzer/` |
83 | | -Roslyn analyzers and code fix providers that detect code patterns and suggest improvements. This project contains: |
84 | | - |
85 | | -#### Analyzers (7 total) |
86 | | -All analyzers inherit from `BaseLinqraftAnalyzer`: |
87 | | -- `AnonymousTypeToDtoAnalyzer.cs`: Detects anonymous types that can be converted to DTOs |
88 | | -- `ApiControllerProducesResponseTypeAnalyzer.cs`: Detects API controllers missing ProducesResponseType attributes |
89 | | -- `LocalVariableCaptureAnalyzer.cs`: Detects local variables that should be captured in SelectExpr |
90 | | -- `SelectExprToTypedAnalyzer.cs`: Detects SelectExpr calls that can use explicit type parameters |
91 | | -- `SelectToSelectExprAnonymousAnalyzer.cs`: Detects Select calls with anonymous types that should use SelectExpr |
92 | | -- `SelectToSelectExprNamedAnalyzer.cs`: Detects Select calls with named types that should use SelectExpr |
93 | | -- `TernaryNullCheckToConditionalAnalyzer.cs`: Detects ternary null checks that can be simplified to null-conditional operators |
94 | | - |
95 | | -#### Code Fix Providers (7 total) |
96 | | -Each analyzer has a corresponding code fix provider: |
97 | | -- `AnonymousTypeToDtoCodeFixProvider.cs` |
98 | | -- `ApiControllerProducesResponseTypeCodeFixProvider.cs` |
99 | | -- `LocalVariableCaptureCodeFixProvider.cs` |
100 | | -- `SelectExprToTypedCodeFixProvider.cs` |
101 | | -- `SelectToSelectExprAnonymousCodeFixProvider.cs` |
102 | | -- `SelectToSelectExprNamedCodeFixProvider.cs` |
103 | | -- `TernaryNullCheckToConditionalCodeFixProvider.cs` |
104 | | - |
105 | | -#### Utilities |
106 | | -- `TernaryNullCheckSimplifier.cs`: Centralized logic for simplifying ternary null checks to null-conditional operators |
107 | | - |
108 | | -### `src/Linqraft.SourceGenerator/` |
109 | | -The Source Generator implementation that performs the actual code generation. Important files include: |
110 | | -- `SelectExprGenerator.cs`: the generator entry point |
111 | | -- `SelectExprGroups.cs`: grouping SelectExpr information (grouped per namespace) |
112 | | -- `SelectExprInfo.cs`: holds information for each SelectExpr and provides the foundation for code generation |
113 | | - - `SelectExprInfoAnonymous.cs`: handles anonymous-type SelectExpr information (pattern 1) |
114 | | - - `SelectExprInfoExplicitDto.cs`: handles explicit DTO SelectExpr information (pattern 2) |
115 | | - - `SelectExprInfoPredefinedDto.cs`: handles pre-existing DTO SelectExpr information (pattern 3) |
116 | | - |
117 | | -### `tests/Linqraft.Tests/` |
118 | | -The test project for source generators. Contains test cases exercising various scenarios and verifies generated output. |
119 | | - |
120 | | -### `tests/Linqraft.Analyzer.Tests/` |
121 | | -The test project for analyzers and code fix providers. Contains comprehensive tests for all 7 analyzers and their corresponding code fixes. |
122 | | - |
123 | | -### `examples/Linqraft.Sample/` |
124 | | -A sample project demonstrating usage examples. |
125 | | - |
126 | | -### `docs/developments/` |
127 | | -Development documentation and guides: |
128 | | -- `refactoring-guide.md`: Comprehensive guide documenting the refactored codebase architecture, helper class organization, code quality standards, and migration guidelines. **Read this before making significant changes to analyzers or helper classes.** |
129 | | - |
130 | | -## Technical background |
| 22 | +### Instant feedback build (runtime library only) |
| 23 | +```bash |
| 24 | +sh scripts/instant-build.sh |
| 25 | +``` |
131 | 26 |
|
132 | | -This project consists of three main components: |
| 27 | +### Quick test (single framework) |
| 28 | +```bash |
| 29 | +sh scripts/clean-test.sh |
| 30 | +``` |
133 | 31 |
|
134 | | -1. **C# Source Generator**: A compile-time code generation feature (available since C# 9). The generator inspects `SelectExpr` calls and emits expression trees and DTO classes. |
| 32 | +### Run specific test |
| 33 | +```bash |
| 34 | +dotnet test --filter "FullyQualifiedName~YourTestName" --no-build |
| 35 | +``` |
135 | 36 |
|
136 | | -2. **Interceptor**: A technique used to intercept method calls and replace the `SelectExpr` call with the generated expression trees at runtime. |
| 37 | +### Inspect generated sources |
| 38 | +1. Run `sh scripts/instant-build.sh` to build the project |
| 39 | +2. Generated code appears in `tests/Linqraft.Tests/.generated/**/*.g.cs` |
137 | 40 |
|
138 | | -3. **Roslyn Analyzers**: Compile-time code analyzers that detect patterns and suggest improvements. Analyzers use the Roslyn API for semantic analysis and syntax tree manipulation. |
| 41 | +## Architecture |
139 | 42 |
|
140 | | -## Build and test |
| 43 | +### Three SelectExpr patterns |
141 | 44 |
|
142 | | -Always perform a clean build to avoid stale generator caches: |
| 45 | +The codebase handles three distinct patterns, each with its own SelectExprInfo implementation: |
143 | 46 |
|
144 | | -```bash |
145 | | -dotnet clean |
146 | | -dotnet build --no-incremental |
147 | | -dotnet test --no-build |
148 | | -``` |
| 47 | +1. **Anonymous pattern** (`SelectExprInfoAnonymous.cs`): `query.SelectExpr(x => new { x.Id, x.Name })` |
| 48 | + - Returns anonymous type |
| 49 | + - No DTO generation needed |
149 | 50 |
|
150 | | -If you want to inspect the generated sources on disk, follow these steps: |
| 51 | +2. **Explicit DTO pattern** (`SelectExprInfoExplicitDto.cs`): `query.SelectExpr<Entity, EntityDto>(x => new { x.Id })` |
| 52 | + - Auto-generates EntityDto class from anonymous type structure |
| 53 | + - Type parameter specifies desired DTO name |
151 | 54 |
|
152 | | -1. Remove the `(test-project)/.generated` directory if it already exists. |
153 | | -2. Enable `EmitCompilerGeneratedFiles` in `Linqraft.Tests.csproj`. |
154 | | -3. The generated code will be emitted to `(test-project)/.generated/**/*.g.cs`. |
| 55 | +3. **Predefined DTO pattern** (`SelectExprInfoPredefinedDto.cs`): `query.SelectExpr(x => new PredefinedDto { x.Id })` |
| 56 | + - Uses existing DTO class |
| 57 | + - No generation, only expression tree creation |
155 | 58 |
|
156 | | -You can use the `./scripts/clean-test.sh` script as a shortcut. |
| 59 | +### Core components |
157 | 60 |
|
158 | | -## Development guidelines |
| 61 | +**Source Generator** (`src/Linqraft.SourceGenerator/`): |
| 62 | +- `SelectExprGenerator.cs`: Entry point, orchestrates generation |
| 63 | +- `SelectExprGroups.cs`: Groups SelectExpr calls by namespace |
| 64 | +- `SelectExprInfo.cs` and subclasses: Parse and hold information for each SelectExpr call |
159 | 65 |
|
160 | | -### Test-driven development recommended |
| 66 | +**Analyzer Infrastructure** (`src/Linqraft.Core/`): |
| 67 | +- `AnalyzerHelpers/`: Analyzer-specific helpers (BaseLinqraftAnalyzer, CaptureHelper, ExpressionHelper, etc.) |
| 68 | +- `RoslynHelpers/`: Roslyn semantic analysis helpers (RoslynTypeHelper for type checking) |
| 69 | +- `SyntaxHelpers/`: Syntax manipulation helpers (TriviaHelper for formatting, NullConditionalHelper) |
161 | 70 |
|
162 | | -- When adding new features, write tests first. |
163 | | -- Verify the generated code in the test project to ensure it matches expectations. |
164 | | -- Ensure all existing tests pass before committing changes. |
165 | | -- For analyzers, add test cases to `tests/Linqraft.Analyzer.Tests/`. |
166 | | -- For source generators, add test cases to `tests/Linqraft.Tests/`. |
| 71 | +**Analyzers** (`src/Linqraft.Analyzer/`): |
| 72 | +- 7 analyzers inheriting from `BaseLinqraftAnalyzer` |
| 73 | +- Each analyzer has a corresponding code fix provider |
| 74 | +- Examples: `SelectToSelectExprAnonymousAnalyzer`, `LocalVariableCaptureAnalyzer`, `TernaryNullCheckToConditionalAnalyzer` |
167 | 75 |
|
168 | | -### Source generator-specific considerations |
| 76 | +**Runtime Library** (`src/Linqraft/`): |
| 77 | +- `DummyExpression.cs`: Marker method for generator detection (do not edit) |
169 | 78 |
|
170 | | -- Cache issues: if changes to the generator are not reflected, run `dotnet clean`. |
171 | | -- IDE restart: if generated code is not visible in Visual Studio or Rider, an IDE restart may be required. |
172 | | -- Debugging: debugging source generators can be more involved than regular code. Use `EmitCompilerGeneratedFiles` to inspect emitted sources when necessary. |
| 79 | +### Key technical details |
173 | 80 |
|
174 | | -### Analyzer development guidelines |
| 81 | +**Null-propagation conversion**: The generator converts `x.Customer?.Name` to `x.Customer != null ? x.Customer.Name : null` in expression trees (EF Core/IQueryable compatible). |
175 | 82 |
|
176 | | -When developing or modifying analyzers: |
| 83 | +**Interceptors**: Uses C# 12 interceptor feature to replace SelectExpr calls with generated code at compile time, enabling zero-runtime-dependency. |
177 | 84 |
|
178 | | -1. **Inherit from BaseLinqraftAnalyzer** |
179 | | - - All new analyzers should inherit from `BaseLinqraftAnalyzer` |
180 | | - - Override the required abstract properties: `DiagnosticId`, `Title`, `MessageFormat`, `Description`, `Severity`, `Rule` |
181 | | - - Define a public const `AnalyzerId` for use by code fix providers |
| 85 | +**Helper class organization**: |
| 86 | +- **RoslynTypeHelper**: Use for semantic type checking (never use string-based type matching) |
| 87 | +- **TriviaHelper**: Preserves formatting and detects cross-platform line endings |
| 88 | +- **BaseLinqraftAnalyzer**: Abstract base for all analyzers, reduces boilerplate |
182 | 89 |
|
183 | | -2. **Use Helper Classes** |
184 | | - - **Always prefer semantic analysis over string matching**: Use `RoslynTypeHelper` instead of string-based type checking |
185 | | - - **Use TriviaHelper for formatting**: Preserve whitespace and comments, and detect line endings for cross-platform compatibility |
186 | | - - **Centralize common patterns**: If you write the same logic twice, extract it to a helper class |
187 | | - - See `docs/developments/refactoring-guide.md` for detailed guidelines on when and how to create helper classes |
| 90 | +Please refer to README.md and docs/library/*.md for more details. |
188 | 91 |
|
189 | | -3. **Code Quality Standards** |
190 | | - ```csharp |
191 | | - // Bad: string-based type checking |
192 | | - if (typeName.EndsWith("?")) { } |
| 92 | +## Development Guidelines |
193 | 93 |
|
194 | | - // Good: semantic analysis |
195 | | - if (RoslynTypeHelper.IsNullableType(typeSymbol)) { } |
196 | | - ``` |
| 94 | +### Analyzer development |
197 | 95 |
|
198 | | - ```csharp |
199 | | - // Bad: hardcoded line endings |
200 | | - var newNode = node.WithTrailingTrivia(SyntaxFactory.EndOfLine("\n")); |
| 96 | +1. **Inherit from BaseLinqraftAnalyzer** for all new analyzers |
| 97 | +2. **Use semantic analysis over string matching**: Always use `RoslynTypeHelper` instead of string-based type checks |
| 98 | +3. **Use TriviaHelper for formatting**: Preserve whitespace/comments and handle cross-platform line endings |
| 99 | +4. **Follow naming conventions**: |
| 100 | + - Analyzers: `*Analyzer.cs` |
| 101 | + - Code fixes: `*CodeFixProvider.cs` |
| 102 | + - Helpers: `*Helper.cs` in appropriate subfolder |
201 | 103 |
|
202 | | - // Good: cross-platform line ending detection |
203 | | - var newNode = node.WithTrailingTrivia(TriviaHelper.EndOfLine(root)); |
204 | | - ``` |
| 104 | +Example: |
| 105 | +```csharp |
| 106 | +// Bad: string-based type checking |
| 107 | +if (typeName.EndsWith("?")) { } |
205 | 108 |
|
206 | | -4. **Follow Naming Conventions** |
207 | | - - Analyzers: `*Analyzer.cs` inheriting from `BaseLinqraftAnalyzer` |
208 | | - - Code Fix Providers: `*CodeFixProvider.cs` |
209 | | - - Analyzer helpers: `*Helper.cs` in `Linqraft.Core/AnalyzerHelpers/` |
210 | | - - Roslyn helpers: `Roslyn*Helper.cs` in `Linqraft.Core/RoslynHelpers/` |
211 | | - - Syntax helpers: `*Helper.cs` in `Linqraft.Core/SyntaxHelpers/` |
| 109 | +// Good: semantic analysis |
| 110 | +if (RoslynTypeHelper.IsNullableType(typeSymbol)) { } |
| 111 | +``` |
212 | 112 |
|
213 | | -5. **Performance Considerations** |
214 | | - - Helper methods are called frequently during analysis |
215 | | - - Keep helper methods lightweight and focused |
216 | | - - Cache expensive operations when possible |
217 | | - - Use `ISymbolEqualityComparer` for symbol comparisons |
| 113 | +### Source generator development |
218 | 114 |
|
219 | | -### Code editing guidelines |
| 115 | +- **Cache issues**: Run `sh scripts/cleanup.sh` if changes aren't reflected |
| 116 | +- **IDE issues**: Restart IDE if generated code isn't visible |
| 117 | +- **Always add/update tests** when modifying generators |
220 | 118 |
|
221 | | -- Do not edit `DummyExpression.cs` (it serves only as a marker). |
222 | | -- When modifying the source generator, always add or update tests. |
223 | | -- When modifying analyzers or code fix providers, always add or update tests. |
224 | | -- Pay attention to the readability and performance of the generated code. |
225 | | -- Document complex helper methods with XML comments. |
226 | | -- Consult `docs/developments/refactoring-guide.md` for architecture guidelines and best practices. |
| 119 | +### Test-driven development |
227 | 120 |
|
228 | | -### Helper class organization |
| 121 | +- Write tests first when adding features |
| 122 | +- Verify generated code matches expectations |
| 123 | +- Ensure all existing tests pass before committing |
| 124 | +- Analyzer tests: `tests/Linqraft.Analyzer.Tests/` |
| 125 | +- Source generator tests: `tests/Linqraft.Tests/` |
229 | 126 |
|
230 | | -Helper classes are organized by purpose: |
| 127 | +## Project Structure |
231 | 128 |
|
232 | 129 | ``` |
233 | | -src/Linqraft.Core/ |
234 | | -├── AnalyzerHelpers/ # Analyzer-specific helpers |
235 | | -│ ├── BaseLinqraftAnalyzer.cs |
236 | | -│ ├── CaptureHelper.cs |
237 | | -│ ├── ExpressionHelper.cs |
238 | | -│ ├── LinqMethodHelper.cs |
239 | | -│ ├── NullCheckHelper.cs |
240 | | -│ ├── SyntaxGenerationHelper.cs |
241 | | -│ ├── SyntaxHelper.cs |
242 | | -│ └── UsingDirectiveHelper.cs |
243 | | -├── RoslynHelpers/ # Roslyn semantic analysis helpers |
244 | | -│ └── RoslynTypeHelper.cs |
245 | | -└── SyntaxHelpers/ # Syntax manipulation helpers |
246 | | - ├── NullConditionalHelper.cs |
247 | | - └── TriviaHelper.cs |
| 130 | +src/ |
| 131 | +├── Linqraft/ # Runtime library (NuGet package) |
| 132 | +├── Linqraft.Core/ # Shared helpers and infrastructure |
| 133 | +│ ├── AnalyzerHelpers/ # Analyzer-specific helpers |
| 134 | +│ ├── RoslynHelpers/ # Semantic analysis helpers |
| 135 | +│ └── SyntaxHelpers/ # Syntax manipulation helpers |
| 136 | +├── Linqraft.Analyzer/ # 7 analyzers + code fix providers |
| 137 | +└── Linqraft.SourceGenerator/ # Source generator implementation |
| 138 | +
|
| 139 | +tests/ |
| 140 | +├── Linqraft.Tests/ # Source generator tests |
| 141 | +└── Linqraft.Analyzer.Tests/ # Analyzer and code fix tests |
| 142 | +
|
| 143 | +examples/ |
| 144 | +├── Linqraft.Sample/ # Basic usage with EF Core |
| 145 | +├── Linqraft.MinimumSample/ # Minimal example |
| 146 | +└── Linqraft.ApiSample/ # API integration example |
248 | 147 | ``` |
249 | | - |
250 | | -**When to create a new helper class:** |
251 | | -1. Same code appears in 3+ locations |
252 | | -2. You're using string-based type checking (use RoslynTypeHelper instead) |
253 | | -3. Repeated syntax patterns (use SyntaxHelper) |
254 | | -4. Complex trivia handling (use TriviaHelper) |
255 | | - |
256 | | -See `docs/developments/refactoring-guide.md` for detailed examples and migration guidelines. |
0 commit comments