Skip to content

Commit 76ba365

Browse files
committed
feat: add AnnotationConstantsGenerator
1 parent cd53286 commit 76ba365

31 files changed

+4944
-25
lines changed

Atc.SourceGenerators.slnx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
<Solution>
22
<Folder Name="/docs/">
3+
<File Path="docs/AnnotationConstantsGenerator-FeatureRoadmap.md" />
4+
<File Path="docs/AnnotationConstantsGenerator-Samples.md" />
5+
<File Path="docs/AnnotationConstantsGenerator.md" />
6+
<File Path="docs/DependencyRegistrationGenerators-FeatureRoadmap.md" />
7+
<File Path="docs/DependencyRegistrationGenerators-Samples.md" />
8+
<File Path="docs/DependencyRegistrationGenerators.md" />
9+
<File Path="docs/EnumMappingGenerators-Samples.md" />
10+
<File Path="docs/EnumMappingGenerators.md" />
11+
<File Path="docs/ObjectMappingGenerators-FeatureRoadmap.md" />
12+
<File Path="docs/ObjectMappingGenerators-Samples.md" />
13+
<File Path="docs/ObjectMappingGenerators.md" />
14+
<File Path="docs/OptionsBindingGenerators-FeatureRoadmap.md" />
15+
<File Path="docs/OptionsBindingGenerators-Samples.md" />
16+
<File Path="docs/OptionsBindingGenerators.md" />
17+
<File Path="docs/PetStoreApi-Samples.md" />
318
<File Path="README.md" />
419
</Folder>
5-
<Folder Name="/docs/generators/">
6-
<File Path="docs/generators/DependencyRegistration.md" />
7-
<File Path="docs/generators/EnumMapping.md" />
8-
<File Path="docs/generators/Mapping.md" />
9-
<File Path="docs/generators/OptionsBinding.md" />
10-
</Folder>
11-
<Folder Name="/docs/samples/">
12-
<File Path="docs/samples/DependencyRegistration.md" />
13-
<File Path="docs/samples/EnumMapping.md" />
14-
<File Path="docs/samples/Mapping.md" />
15-
<File Path="docs/samples/OptionsBinding.md" />
16-
<File Path="docs/samples/PetStoreApi.md" />
17-
</Folder>
1820
<Folder Name="/sample/">
21+
<Project Path="sample/Atc.SourceGenerators.AnnotationConstants/Atc.SourceGenerators.AnnotationConstants.csproj" />
1922
<Project Path="sample/Atc.SourceGenerators.DependencyRegistration.Domain/Atc.SourceGenerators.DependencyRegistration.Domain.csproj" />
2023
<Project Path="sample/Atc.SourceGenerators.DependencyRegistration/Atc.SourceGenerators.DependencyRegistration.csproj" />
2124
<Project Path="sample/Atc.SourceGenerators.EnumMapping/Atc.SourceGenerators.EnumMapping.csproj" />

CLAUDE.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
This is a **Roslyn C# Source Generators** project that provides compile-time code generation for .NET applications. The solution contains three main source generators:
7+
This is a **Roslyn C# Source Generators** project that provides compile-time code generation for .NET applications. The solution contains four main source generators:
88

99
1. **DependencyRegistrationGenerator** - Automatically generates dependency injection service registrations
1010
2. **OptionsBindingGenerator** - Automatically generates configuration options binding code
1111
3. **MappingGenerator** - Automatically generates type-safe object-to-object mapping code
12+
4. **AnnotationConstantsGenerator** - Automatically generates compile-time constants from DataAnnotation attributes
1213

1314
All generators eliminate boilerplate code and improve developer productivity while maintaining Native AOT compatibility.
1415

@@ -20,6 +21,7 @@ src/
2021
DependencyRegistrationGenerator.cs
2122
OptionsBindingGenerator.cs
2223
ObjectMappingGenerator.cs
24+
AnnotationConstantsGenerator.cs
2325
RuleIdentifierConstants.cs # Diagnostic ID constants
2426
RuleCategoryConstants.cs # Diagnostic category constants
2527
Atc.SourceGenerators.Annotations/ # Shared attribute definitions (published as separate package)
@@ -34,6 +36,7 @@ test/
3436
DependencyRegistrationGeneratorTests.cs
3537
OptionsBindingGeneratorTests.cs
3638
ObjectMappingGeneratorTests.cs
39+
AnnotationConstantsGeneratorTests.cs
3740
3841
sample/
3942
Atc.SourceGenerators.DependencyRegistration/ # DI registration sample
@@ -43,6 +46,7 @@ sample/
4346
Atc.SourceGenerators.Mapping/ # Object mapping API sample
4447
Atc.SourceGenerators.Mapping.Domain/ # Domain models with mappings (includes BaseEntity/AuditableEntity/Book for inheritance demo)
4548
Atc.SourceGenerators.Mapping.DataAccess/ # Database entities with mappings
49+
Atc.SourceGenerators.AnnotationConstants/ # DataAnnotation constants sample
4650
PetStore.Api/ # Complete 3-layer ASP.NET Core API with OpenAPI/Scalar
4751
PetStore.Api.Contract/ # API contracts (DTOs)
4852
PetStore.Domain/ # Domain layer using all generators
@@ -972,6 +976,98 @@ PetStatus (API)
972976
- `ATCENUM001` - Target type must be an enum (Error)
973977
- `ATCENUM002` - Source enum value has no matching target value (Warning)
974978

979+
### AnnotationConstantsGenerator
980+
981+
**Key Features:**
982+
- Automatic scanning of classes/records with DataAnnotation attributes
983+
- No opt-in attribute required - scans all types automatically
984+
- Full support for 17 Microsoft DataAnnotation attributes
985+
- **Atc attributes support** - IPAddress, Uri, String, KeyString, IsoCurrencySymbol, IgnoreDisplay, EnumGuid, CasingStyleDescription (when Atc package is referenced)
986+
- Zero reflection at runtime - all metadata accessible at compile time
987+
- Native AOT compatible
988+
- Configurable via .editorconfig (`atc_annotation_constants.include_unannotated_properties`)
989+
990+
**Supported Microsoft DataAnnotation Attributes:**
991+
- Display: DisplayName, Description, ShortName, GroupName, Prompt, Order
992+
- Validation: Required (with AllowEmptyStrings, ErrorMessage), StringLength, MinLength, MaxLength, Range, RegularExpression
993+
- Data Types: EmailAddress, Phone, Url, CreditCard, DataType
994+
- Metadata: Key, Compare, Editable, ScaffoldColumn, Timestamp
995+
996+
**Supported Atc Attributes (from Atc package):**
997+
- `IPAddressAttribute` - IsIPAddress, IPAddressRequired
998+
- `IsoCurrencySymbolAttribute` - IsIsoCurrencySymbol, IsoCurrencySymbolRequired, AllowedIsoCurrencySymbols
999+
- `StringAttribute` - IsAtcString, AtcStringRequired, AtcStringMinLength, AtcStringMaxLength, AtcStringRegularExpression, AtcStringInvalidCharacters, AtcStringInvalidPrefixStrings
1000+
- `KeyStringAttribute` - IsKeyString (plus all StringAttribute constants)
1001+
- `UriAttribute` - IsAtcUri, AtcUriRequired, AtcUriAllowHttp, AtcUriAllowHttps, AtcUriAllowFtp, AtcUriAllowFtps, AtcUriAllowFile, AtcUriAllowOpcTcp
1002+
- `IgnoreDisplayAttribute` - IsIgnoreDisplay
1003+
- `EnumGuidAttribute` - EnumGuid
1004+
- `CasingStyleDescriptionAttribute` - CasingStyleDefault, CasingStylePrefix
1005+
1006+
**Generated Code Pattern:**
1007+
```csharp
1008+
// Input:
1009+
using System.ComponentModel.DataAnnotations;
1010+
1011+
public class Product
1012+
{
1013+
[Display(Name = "Product Name", Description = "The display name of the product")]
1014+
[Required]
1015+
[StringLength(100)]
1016+
public string Name { get; set; } = string.Empty;
1017+
1018+
[Display(Name = "Price")]
1019+
[Required]
1020+
[Range(typeof(decimal), "0.01", "999999.99")]
1021+
public decimal Price { get; set; }
1022+
}
1023+
1024+
// Output (static partial class in same namespace):
1025+
namespace MyNamespace;
1026+
1027+
public static partial class AnnotationConstants
1028+
{
1029+
public static partial class Product
1030+
{
1031+
public static partial class Name
1032+
{
1033+
public const string DisplayName = "Product Name";
1034+
public const string Description = "The display name of the product";
1035+
public const bool IsRequired = true;
1036+
public const int MaximumLength = 100;
1037+
}
1038+
1039+
public static partial class Price
1040+
{
1041+
public const string DisplayName = "Price";
1042+
public const bool IsRequired = true;
1043+
public const string Minimum = "0.01";
1044+
public const string Maximum = "999999.99";
1045+
public static readonly System.Type OperandType = typeof(decimal);
1046+
}
1047+
}
1048+
}
1049+
1050+
// Usage:
1051+
string displayName = AnnotationConstants.Product.Name.DisplayName; // "Product Name"
1052+
bool isRequired = AnnotationConstants.Product.Name.IsRequired; // true
1053+
int maxLength = AnnotationConstants.Product.Name.MaximumLength; // 100
1054+
string priceMin = AnnotationConstants.Product.Price.Minimum; // "0.01"
1055+
Type priceType = AnnotationConstants.Product.Price.OperandType; // typeof(decimal)
1056+
```
1057+
1058+
**Configuration via .editorconfig:**
1059+
```ini
1060+
# Include properties without annotations (default: false)
1061+
atc_annotation_constants.include_unannotated_properties = true
1062+
```
1063+
1064+
**Use Cases:**
1065+
- Blazor dynamic forms without reflection
1066+
- Client-side validation metadata
1067+
- API documentation generation
1068+
- TypeScript type generation
1069+
- UI label/placeholder generation
1070+
9751071
## PetStore Sample - Complete Example
9761072

9771073
The `PetStore.Api` sample demonstrates all four generators working together in a realistic 3-layer ASP.NET Core application with OpenAPI/Scalar documentation.

README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ A collection of Roslyn C# source generators for .NET that eliminate boilerplate
1717
- **[⚙️ OptionsBindingGenerator](#️-optionsbindinggenerator)** - Automatic configuration binding to strongly-typed options classes
1818
- **[🗺️ MappingGenerator](#️-mappinggenerator)** - Automatic object-to-object mapping with type safety
1919
- **[🔄 EnumMappingGenerator](#-enummappinggenerator)** - Automatic enum-to-enum mapping with intelligent matching
20+
- **[📋 AnnotationConstantsGenerator](#-annotationconstantsgenerator)** - Compile-time access to DataAnnotation metadata
2021

2122
## ✨ See It In Action
2223

@@ -808,6 +809,108 @@ Get errors and warnings at compile time, not runtime:
808809

809810
---
810811

812+
### 📋 AnnotationConstantsGenerator
813+
814+
Access DataAnnotation attribute metadata at compile time without reflection. The generator automatically scans classes and records for `System.ComponentModel.DataAnnotations` attributes and generates strongly-typed constants.
815+
816+
#### 📚 Documentation
817+
818+
- **[Annotation Constants Guide](docs/AnnotationConstantsGenerator.md)** - Full documentation with examples
819+
- **[Sample Projects](docs/AnnotationConstantsGenerator-Samples.md)** - Working code examples
820+
821+
#### 😫 From This
822+
823+
```csharp
824+
// Runtime reflection to access annotation metadata 😫
825+
var nameProperty = typeof(Product).GetProperty("Name");
826+
var displayAttr = nameProperty.GetCustomAttribute<DisplayAttribute>();
827+
var displayName = displayAttr?.Name; // "Product Name"
828+
829+
var maxLengthAttr = nameProperty.GetCustomAttribute<StringLengthAttribute>();
830+
var maxLength = maxLengthAttr?.MaximumLength; // 100
831+
832+
// ... more reflection for each property
833+
// ... slow, not AOT compatible
834+
// ... no IntelliSense
835+
```
836+
837+
#### ✨ To This
838+
839+
```csharp
840+
// Your model - No special attribute needed ✨
841+
using System.ComponentModel.DataAnnotations;
842+
843+
public class Product
844+
{
845+
[Display(Name = "Product Name", Description = "The product name")]
846+
[Required]
847+
[StringLength(100)]
848+
public string Name { get; set; } = string.Empty;
849+
850+
[Display(Name = "Price")]
851+
[Required]
852+
[Range(typeof(decimal), "0.01", "999999.99")]
853+
public decimal Price { get; set; }
854+
}
855+
856+
// Access via generated constants - Zero reflection! ✨
857+
string displayName = AnnotationConstants.Product.Name.DisplayName; // "Product Name"
858+
string description = AnnotationConstants.Product.Name.Description; // "The product name"
859+
bool isRequired = AnnotationConstants.Product.Name.IsRequired; // true
860+
int maxLength = AnnotationConstants.Product.Name.MaximumLength; // 100
861+
862+
string priceMin = AnnotationConstants.Product.Price.Minimum; // "0.01"
863+
string priceMax = AnnotationConstants.Product.Price.Maximum; // "999999.99"
864+
Type priceType = AnnotationConstants.Product.Price.OperandType; // typeof(decimal)
865+
```
866+
867+
#### ✨ Key Features
868+
869+
- **🔍 Automatic Scanning**: No opt-in attribute needed - scans all classes with DataAnnotation attributes
870+
- **📦 Full DataAnnotations Support**: Display, Required, StringLength, Range, RegularExpression, EmailAddress, Phone, Url, CreditCard, DataType, Compare, Key, Editable, ScaffoldColumn, Timestamp
871+
- **🔵 Atc Attributes Support**: IPAddress, Uri, String, KeyString, IsoCurrencySymbol, IgnoreDisplay, EnumGuid, CasingStyleDescription (when Atc package is referenced)
872+
- **⚡ Zero Reflection**: All metadata accessible at compile time
873+
- **🚀 Native AOT Compatible**: Fully trimming-safe, no runtime type inspection
874+
- **🛡️ Type-Safe**: Full IntelliSense support with compile-time validation
875+
- **⚙️ Configurable**: Optional `.editorconfig` setting to include unannotated properties
876+
- **🎯 Perfect for Blazor**: Build dynamic forms without reflection
877+
- **📝 API Documentation**: Generate OpenAPI schemas at compile time
878+
879+
#### 🚀 Quick Example
880+
881+
```csharp
882+
using System.ComponentModel.DataAnnotations;
883+
884+
public class Customer
885+
{
886+
[Display(Name = "Full Name", Order = 1)]
887+
[Required]
888+
[StringLength(200)]
889+
public string FullName { get; set; } = string.Empty;
890+
891+
[Display(Name = "Email Address", Order = 2)]
892+
[Required]
893+
[EmailAddress]
894+
public string Email { get; set; } = string.Empty;
895+
896+
[Key]
897+
[Editable(false)]
898+
public Guid Id { get; set; }
899+
}
900+
901+
// ✨ Generated constants - use in validation, UI, docs
902+
Console.WriteLine(AnnotationConstants.Customer.FullName.DisplayName); // "Full Name"
903+
Console.WriteLine(AnnotationConstants.Customer.FullName.Order); // 1
904+
Console.WriteLine(AnnotationConstants.Customer.FullName.IsRequired); // true
905+
Console.WriteLine(AnnotationConstants.Customer.FullName.MaximumLength); // 200
906+
907+
Console.WriteLine(AnnotationConstants.Customer.Email.IsEmailAddress); // true
908+
Console.WriteLine(AnnotationConstants.Customer.Id.IsKey); // true
909+
Console.WriteLine(AnnotationConstants.Customer.Id.IsEditable); // false
910+
```
911+
912+
---
913+
811914
## 🔨 Building
812915

813916
```bash
@@ -860,6 +963,15 @@ cd sample/Atc.SourceGenerators.EnumMapping
860963
dotnet run
861964
```
862965

966+
### 📋 [AnnotationConstants Sample](docs/AnnotationConstantsGenerator-Samples.md)
967+
968+
Console app demonstrating compile-time access to DataAnnotation metadata without reflection. Shows how to access Display names, validation rules, and data type constraints via generated constants.
969+
970+
```bash
971+
cd sample/Atc.SourceGenerators.AnnotationConstants
972+
dotnet run
973+
```
974+
863975
### 🎯 [PetStore API - Complete Example](docs/PetStoreApi-Samples.md)
864976

865977
Full-featured ASP.NET Core application using **all four generators** together with OpenAPI/Scalar documentation. This demonstrates production-ready patterns for modern .NET applications.

0 commit comments

Comments
 (0)