Skip to content

Commit 807ec07

Browse files
committed
Improvements, refactoring and tests
1 parent 6dc8eea commit 807ec07

1,953 files changed

Lines changed: 143377 additions & 4729 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/agents/feature-reviewer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ When reviewing a newly implemented feature, you will:
2020

2121
**For Smart Enums:**
2222
- Confirm the type is marked `partial` and uses appropriate attribute (`[SmartEnum]` or `[SmartEnum<TKey>]`)
23-
- Verify items are public static readonly/const fields or properties
23+
- Verify items are public static readonly fields
2424
- Check that keyed enums specify appropriate key member configuration
2525
- Ensure sealed modifier is present if no derived types exist
2626
- Validate that comparison operators are only used with keyed enums

.clinerules

Lines changed: 477 additions & 0 deletions
Large diffs are not rendered by default.

.github/copilot-instructions.md

Lines changed: 472 additions & 150 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 159 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
# CLAUDE.md
2-
3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
1+
This file provides guidance to the AI assistant when working with code in this repository.
42

53
## Common Commands
64

75
### Build and Test
8-
- **Build solution**: `dotnet build --configuration Release`
6+
- **Build solution**: `dotnet build`
97
- **Restore packages**: `dotnet restore`
10-
- **Run all tests**: `dotnet test --configuration Release`
11-
- **Pack NuGet packages**: `dotnet pack --configuration Release --output out`
12-
- **Format code**: `dotnet format`
8+
- **Run all tests**: `dotnet test`
139

1410
### Development Requirements
1511
- .NET 9.0 SDK (as specified in global.json)
@@ -76,7 +72,7 @@ The `Thinktecture.Runtime.Extensions.SourceGenerator` project contains multiple
7672

7773
#### Analyzers
7874
1. **`ThinktectureRuntimeExtensionsAnalyzer`**: Main diagnostic analyzer with 40+ diagnostic rules that validates correct usage of library features
79-
- **Type structure**: Ensures types with `[SmartEnum]`, `[ValueObject]`, or `[Union]` are `partial`, are class/struct, not generic (except regular unions and complex value objects), not nested in generic types
75+
- **Type structure**: Ensures types with `[SmartEnum]`, `[ValueObject]`, `[Union]`, or `[AdHocUnion]` are `partial`, are class/struct, not generic (except regular unions and complex value objects), not nested in generic types
8076
- **Constructor rules**: Validates constructors are private, no primary constructors allowed
8177
- **Member validation**:
8278
- Fields must be readonly, properties must be readonly (no set) or have private init
@@ -106,7 +102,7 @@ The `Thinktecture.Runtime.Extensions.SourceGenerator` project contains multiple
106102

107103
#### 1. Smart Enums
108104
**Keyless**: `[SmartEnum]` - Type-safe enums without underlying values
109-
- Items are defined as public static readonly/const fields or properties
105+
- Items are defined as public static readonly fields
110106
- Cannot use comparison operators (no key to compare)
111107
- Supports `Switch`/`Map` methods, equality operators, serialization
112108
- Must be sealed if no derived types
@@ -152,7 +148,7 @@ The `Thinktecture.Runtime.Extensions.SourceGenerator` project contains multiple
152148
- Validation: Implement `ValidateFactoryArguments` (preferred) or `ValidateConstructorArguments`
153149

154150
#### 3. Discriminated Unions
155-
**Ad-hoc Unions**: `[Union<T1, T2>]` through `[Union<T1, T2, T3, T4, T5>]` OR `[AdHocUnion(typeof(T1), typeof(T2), ...)]`
151+
**Ad-hoc Unions**: `[Union<T1, T2>]` through `[Union<T1, T2, T3, T4, T5>]`, or non generic `[AdHocUnion(Type t1, Type t2, Type? t3, Type? t4, Type? t5)]`
156152
- Simple combination of 2-5 types without inheritance
157153
- Two syntaxes available:
158154
- **Generic** (recommended): `[Union<string, int>]` - simpler syntax for most cases
@@ -283,15 +279,22 @@ The source generators follow a consistent pattern:
283279

284280
## Testing Strategy
285281
- xUnit for unit testing
286-
- AwesomeAssertions for readable assertions
287-
- Verify.Xunit for snapshot testing
282+
- Analyze whether `Theory` can be used for parameterized tests, before considering separate tests for each case
283+
- Edge cases must be tested as well
284+
- AwesomeAssertions for readable assertions (uses same api as FluentAssertions)
285+
- Verify.Xunit for snapshot testing (useful for generated code verification)
288286
- Comprehensive tests for generated code, serialization, and framework integration
287+
- Follow Arrange-Act-Assert pattern for unit tests
288+
- The tests should have same folder structure as the source code (e.g., with class `Ns1/Ns2/MyClass.cs` -> `Ns1/Ns2/MyCassTests/MyMethod.cs`)
289+
- Create a separate test class/file for every public member (eg. for class `MyClass` with method `MyMethod` create `MyCassTests/MyMethod.cs`)
290+
- If the tests requires a `CSharpCompilation` use base class `test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/CompilationTestBase.cs`
291+
- When testing attributes, use real attributes, like `SmartEnumAttribute`, `ValueObjectAttribute`, `ComplexValueObjectAttribute`, `UnionAttribute`, `ObjectFactoryAttribute`, etc. Don't use fake attributes.
289292

290293
## Code Style
291294
- Follow `.editorconfig` settings (especially in `src/.editorconfig`)
292-
- **XML documentation required** for all publicly visible types and members (except source generator, test, and sample projects)
295+
- **XML documentation required** for all publicly visible types and members (except source generator, analyzer, test, and sample projects)
293296
- Multi-target framework support (net7.0 base, with EF Core version-specific projects)
294-
- Use `dotnet format` to format the entire solution
297+
- Don't use `#region`/`#endregion`
295298

296299
## Common Troubleshooting and Best Practices
297300

@@ -301,14 +304,14 @@ The source generators follow a consistent pattern:
301304
3. **Null handling**: For nullable key types, consider using `NullInFactoryMethodsYieldsNull` or `EmptyStringInFactoryMethodsYieldsNull` instead of throwing exceptions
302305
4. **Immutability**: All members should be readonly (fields) or have no setter/private init (properties)
303306
5. **Constructors**: Keep constructors private to enforce use of factory methods (`Create`, `TryCreate`)
304-
6. **Smart Enum items**: Must be public static readonly/const - non-static properties won't be recognized as items
307+
6. **Smart Enum items**: Must be public static readonly fields - non-static fields or properties won't be recognized as items
305308
7. **Serialization**: Reference integration packages in the same project as your types for automatic code generation, or manually register converter factories
306309
8. **Partial keyword**: Types must be marked `partial` for source generators to work
307310

308311
### Common Issues
309312
1. **"Type must be partial"**: Add `partial` keyword to your class/struct declaration
310313
2. **"String-based value object needs equality comparer"**: Add `[KeyMemberEqualityComparer<MyType, string, StringComparer>]` attribute
311-
3. **"Smart enum has no items"**: Ensure items are public static readonly/const fields or properties of the enum type
314+
3. **"Smart enum has no items"**: Ensure items are public static readonly fields of the enum type
312315
4. **"Custom key member implementation not found"**: When using `SkipKeyMember = true`, ensure you've implemented the key member with the correct name and type
313316
5. **"Members disallowing default values must be required"**: Properties with `IDisallowDefaultValue` constraint must use `required` keyword or be initialized
314317
6. **Serialization not working**: Ensure integration package is referenced, or manually register converters/formatters
@@ -332,3 +335,143 @@ The source generators follow a consistent pattern:
332335
- **Result<T>**: Generic union for success/failure without exceptions (`Success(T Value)`, `Failure(string Error)`)
333336
- **PartiallyKnownDate**: Regular union for dates with varying precision (year only, year/month, full date)
334337
- **Jurisdiction**: Complex domain modeling combining unions and value objects
338+
339+
---
340+
341+
## Attribute Configuration Reference
342+
343+
This section lists constructor arguments and configurable properties for key attributes.
344+
345+
Notes:
346+
- Defaults shown reflect runtime defaults from constructors or property getters.
347+
- Some properties only take effect under certain conditions (see descriptions).
348+
349+
### Base Attributes (common configuration)
350+
351+
- ValueObjectAttributeBase (base for ValueObjectAttribute<TKey>, ComplexValueObjectAttribute)
352+
- Properties:
353+
- SkipFactoryMethods: bool — do not generate Create/TryCreate/Validate
354+
- ConstructorAccessModifier: AccessModifier — default Private
355+
- CreateFactoryMethodName: string — default "Create" (whitespace resets to default)
356+
- TryCreateFactoryMethodName: string — default "TryCreate" (whitespace resets to default)
357+
- SkipToString: bool — skip ToString() generation
358+
- AllowDefaultStructs: bool — allow default(struct) instances (structs only)
359+
- DefaultInstancePropertyName: string — default "Empty" (structs only)
360+
- SerializationFrameworks: SerializationFrameworks — default All
361+
362+
- UnionAttributeBase (base for AdHocUnionAttribute and generic UnionAttribute<...>)
363+
- Properties:
364+
- DefaultStringComparison: StringComparison — default OrdinalIgnoreCase
365+
- SkipToString: bool — skip ToString() generation
366+
- ConstructorAccessModifier: UnionConstructorAccessModifier — default Public
367+
- ConversionFromValue: ConversionOperatorsGeneration — default Implicit
368+
- ConversionToValue: ConversionOperatorsGeneration — default Explicit
369+
- SwitchMethods: SwitchMapMethodsGeneration — configure Switch generation
370+
- MapMethods: SwitchMapMethodsGeneration — configure Map generation
371+
- SwitchMapStateParameterName: string? — default "state"
372+
- UseSingleBackingField: bool — default false
373+
374+
### SmartEnumAttribute (keyless)
375+
- Targets: class
376+
- Constructors:
377+
- SmartEnumAttribute()
378+
- Properties:
379+
- EqualityComparisonOperators: OperatorsGeneration — generation for equality operators
380+
- SwitchMethods: SwitchMapMethodsGeneration — Switch generation
381+
- MapMethods: SwitchMapMethodsGeneration — Map generation
382+
- SwitchMapStateParameterName: string? — default "state"
383+
384+
### SmartEnumAttribute<TKey>
385+
- Targets: class; where TKey : notnull
386+
- Constructors:
387+
- SmartEnumAttribute() — sets defaults
388+
- Properties:
389+
- KeyMemberType: Type — default typeof(TKey)
390+
- KeyMemberAccessModifier: AccessModifier — default Public
391+
- KeyMemberKind: MemberKind — default Property
392+
- KeyMemberName: string — default "_key" if private field; otherwise "Key"
393+
- SkipIComparable: bool — skip IComparable<T> when key not comparable
394+
- SkipIParsable: bool — skip IParsable<T> when key not string/IParsable
395+
- ComparisonOperators: OperatorsGeneration — comparison operators (depends on EqualityComparisonOperators)
396+
- EqualityComparisonOperators: OperatorsGeneration — equality operators; coerced to at least ComparisonOperators
397+
- SkipIFormattable: bool — skip IFormattable when key not IFormattable
398+
- SkipToString: bool — skip ToString()
399+
- SwitchMethods: SwitchMapMethodsGeneration
400+
- MapMethods: SwitchMapMethodsGeneration
401+
- ConversionToKeyMemberType: ConversionOperatorsGeneration — default Implicit
402+
- ConversionFromKeyMemberType: ConversionOperatorsGeneration — default Explicit
403+
- SerializationFrameworks: SerializationFrameworks — default All
404+
- SwitchMapStateParameterName: string? — default "state"
405+
406+
### ValueObjectAttribute<TKey>
407+
- Targets: class | struct; where TKey : notnull
408+
- Inherits: ValueObjectAttributeBase
409+
- Constructors:
410+
- ValueObjectAttribute() — sets defaults
411+
- Properties:
412+
- KeyMemberType: Type — readonly, typeof(TKey)
413+
- KeyMemberAccessModifier: AccessModifier — default Private
414+
- KeyMemberKind: MemberKind — default Field
415+
- KeyMemberName: string — default "_value" if private field; otherwise "Value"
416+
- SkipKeyMember: bool — do not generate key member; implement manually (use KeyMemberName)
417+
- NullInFactoryMethodsYieldsNull: bool — Create/TryCreate/Validate return null on null input (class + factories only; implied true if EmptyStringInFactoryMethodsYieldsNull is true)
418+
- EmptyStringInFactoryMethodsYieldsNull: bool — string-key empty/whitespace yields null (class + factories only)
419+
- SkipIComparable: bool — skip if key not comparable and no custom comparer
420+
- SkipIParsable: bool — skip if factories skipped or key not string/IParsable
421+
- AdditionOperators: OperatorsGeneration — requires key supports these ops
422+
- SubtractionOperators: OperatorsGeneration — requires key supports these ops
423+
- MultiplyOperators: OperatorsGeneration — requires key supports these ops
424+
- DivisionOperators: OperatorsGeneration — requires key supports these ops
425+
- ComparisonOperators: OperatorsGeneration — depends on EqualityComparisonOperators
426+
- EqualityComparisonOperators: OperatorsGeneration — coerced to at least ComparisonOperators
427+
- SkipIFormattable: bool — skip if key not IFormattable
428+
- ConversionToKeyMemberType: ConversionOperatorsGeneration — default Implicit
429+
- UnsafeConversionToKeyMemberType: ConversionOperatorsGeneration — default Explicit (may throw)
430+
- ConversionFromKeyMemberType: ConversionOperatorsGeneration — default Explicit
431+
432+
### ComplexValueObjectAttribute
433+
- Targets: class | struct
434+
- Inherits: ValueObjectAttributeBase
435+
- Properties:
436+
- DefaultStringComparison: StringComparison — default OrdinalIgnoreCase
437+
438+
### UnionAttribute<T1, T2>, ..., UnionAttribute<T1, T2, T3, T4, T5>
439+
- Targets: class | struct
440+
- Inherits: UnionAttributeBase
441+
- Properties (per generic parameter):
442+
- T1Name, T2Name, ...: string? — override member name (default: type name)
443+
- T1IsNullableReferenceType, T2IsNullableReferenceType, ...: bool — mark as nullable reference type (no effect for structs)
444+
445+
### AdHocUnionAttribute (non-generic version of UnionAttribute<T1, T2, ...>)
446+
- Targets: class | struct
447+
- Inherits: UnionAttributeBase
448+
- Constructors:
449+
- AdHocUnionAttribute(Type t1, Type t2, Type? t3 = null, Type? t4 = null, Type? t5 = null)
450+
- Properties:
451+
- T1, T2: Type — required member types
452+
- T3, T4, T5: Type? — optional member types
453+
- T1Name..T5Name: string? — override member names (default: type name)
454+
- T1IsNullableReferenceType..T5IsNullableReferenceType: bool — mark as nullable reference type (no effect for structs)
455+
456+
### UnionAttribute (regular inheritance-based unions)
457+
- Targets: class
458+
- Constructors:
459+
- UnionAttribute() — sets ConversionFromValue = Implicit
460+
- Properties:
461+
- SwitchMethods: SwitchMapMethodsGeneration
462+
- MapMethods: SwitchMapMethodsGeneration
463+
- ConversionFromValue: ConversionOperatorsGeneration — default Implicit
464+
- SwitchMapStateParameterName: string? — default "state"
465+
466+
### ObjectFactoryAttribute<T>
467+
- Targets: class | struct; AllowMultiple = true
468+
- Constructors:
469+
- ObjectFactoryAttribute() — configures factory for typeof(T)
470+
- Properties:
471+
- Type: Type — value type the factory accepts (readonly; typeof(T))
472+
- UseForSerialization: SerializationFrameworks — frameworks that should use the factory/type
473+
- UseWithEntityFramework: bool — enable EF Core integration for this factory
474+
- UseForModelBinding: bool (init-only) — enable ASP.NET Core model binding
475+
- HasCorrespondingConstructor: bool (init-only) — indicates presence of a single-parameter ctor of type `Type`
476+
- Obsolete alias:
477+
- ValueObjectFactoryAttribute<T> — use ObjectFactoryAttribute<T> instead

0 commit comments

Comments
 (0)