Skip to content

Commit ef3ac28

Browse files
committed
Improvements, refactoring and tests
1 parent 57ae2f3 commit ef3ac28

949 files changed

Lines changed: 61326 additions & 4116 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: 470 additions & 0 deletions
Large diffs are not rendered by default.

.github/copilot-instructions.md

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

CLAUDE.md

Lines changed: 160 additions & 17 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)
@@ -22,7 +18,7 @@ This is a .NET library providing **Smart Enums**, **Value Objects**, and **Discr
2218

2319
### Core Components
2420
- **`src/Thinktecture.Runtime.Extensions`**: Core library with base interfaces, attributes, and runtime helpers
25-
- Attributes: `SmartEnumAttribute<TKey>`, `SmartEnumAttribute`, `ValueObjectAttribute<TKey>`, `ComplexValueObjectAttribute`, `UnionAttribute<T1,T2,...>` (up to 5 type parameters), `UnionAttribute`, `ObjectFactoryAttribute<T>`
21+
- Attributes: `SmartEnumAttribute<TKey>`, `SmartEnumAttribute`, `ValueObjectAttribute<TKey>`, `ComplexValueObjectAttribute`, `UnionAttribute<T1,T2,...>` (up to 5 type parameters), `UnionAttribute`, `AdHocUnionAttribute`, `ObjectFactoryAttribute<T>`
2622
- Additional attributes: `KeyMemberEqualityComparerAttribute`, `KeyMemberComparerAttribute`, `MemberEqualityComparerAttribute`, `IgnoreMemberAttribute`, `ValidationErrorAttribute`, `UseDelegateFromConstructorAttribute`, `UnionSwitchMapOverloadAttribute`
2723
- Key interfaces: `ISmartEnum<TKey>`, `IEnum`, `IKeyedObject<TKey>`, `IKeyedValueObject<TKey>`, `IComplexValueObject`, `IValidationError`, `IObjectFactory<T>`
2824
- Utility types: `ValidationError`, `ComparerAccessors`, collection helpers (EmptyDictionary, EmptyLookup, SingleItemReadOnlyDictionary, etc.)
@@ -75,7 +71,7 @@ The `Thinktecture.Runtime.Extensions.SourceGenerator` project contains multiple
7571

7672
#### Analyzers
7773
1. **`ThinktectureRuntimeExtensionsAnalyzer`**: Main diagnostic analyzer with 40+ diagnostic rules that validates correct usage of library features
78-
- **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
74+
- **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
7975
- **Constructor rules**: Validates constructors are private, no primary constructors allowed
8076
- **Member validation**:
8177
- Fields must be readonly, properties must be readonly (no set) or have private init
@@ -105,7 +101,7 @@ The `Thinktecture.Runtime.Extensions.SourceGenerator` project contains multiple
105101

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

152148
#### 3. Discriminated Unions
153-
**Ad-hoc Unions**: `[Union<T1, T2>]` through `[Union<T1, T2, T3, T4, T5>]`
149+
**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)]`
154150
- Simple combination of 2-5 types without inheritance
155151
- Generates implicit conversion operators from each type to union (configurable via `ConversionFromValue`)
156152
- Type checking: `IsT1`, `IsT2`, etc. properties
@@ -276,15 +272,22 @@ The source generators follow a consistent pattern:
276272

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

283286
## Code Style
284287
- Follow `.editorconfig` settings (especially in `src/.editorconfig`)
285-
- **XML documentation required** for all publicly visible types and members (except source generator, test, and sample projects)
288+
- **XML documentation required** for all publicly visible types and members (except source generator, analyzer, test, and sample projects)
286289
- Multi-target framework support (net7.0 base, with EF Core version-specific projects)
287-
- Use `dotnet format` to format the entire solution
290+
- Don't use `#region`/`#endregion`
288291

289292
## Common Troubleshooting and Best Practices
290293

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

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

0 commit comments

Comments
 (0)