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
29 changes: 16 additions & 13 deletions Atc.SourceGenerators.slnx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
<Solution>
<Folder Name="/docs/">
<File Path="docs/AnnotationConstantsGenerator-FeatureRoadmap.md" />
<File Path="docs/AnnotationConstantsGenerator-Samples.md" />
<File Path="docs/AnnotationConstantsGenerator.md" />
<File Path="docs/DependencyRegistrationGenerators-FeatureRoadmap.md" />
<File Path="docs/DependencyRegistrationGenerators-Samples.md" />
<File Path="docs/DependencyRegistrationGenerators.md" />
<File Path="docs/EnumMappingGenerators-Samples.md" />
<File Path="docs/EnumMappingGenerators.md" />
<File Path="docs/ObjectMappingGenerators-FeatureRoadmap.md" />
<File Path="docs/ObjectMappingGenerators-Samples.md" />
<File Path="docs/ObjectMappingGenerators.md" />
<File Path="docs/OptionsBindingGenerators-FeatureRoadmap.md" />
<File Path="docs/OptionsBindingGenerators-Samples.md" />
<File Path="docs/OptionsBindingGenerators.md" />
<File Path="docs/PetStoreApi-Samples.md" />
<File Path="README.md" />
</Folder>
<Folder Name="/docs/generators/">
<File Path="docs/generators/DependencyRegistration.md" />
<File Path="docs/generators/EnumMapping.md" />
<File Path="docs/generators/Mapping.md" />
<File Path="docs/generators/OptionsBinding.md" />
</Folder>
<Folder Name="/docs/samples/">
<File Path="docs/samples/DependencyRegistration.md" />
<File Path="docs/samples/EnumMapping.md" />
<File Path="docs/samples/Mapping.md" />
<File Path="docs/samples/OptionsBinding.md" />
<File Path="docs/samples/PetStoreApi.md" />
</Folder>
<Folder Name="/sample/">
<Project Path="sample/Atc.SourceGenerators.AnnotationConstants/Atc.SourceGenerators.AnnotationConstants.csproj" />
<Project Path="sample/Atc.SourceGenerators.DependencyRegistration.Domain/Atc.SourceGenerators.DependencyRegistration.Domain.csproj" />
<Project Path="sample/Atc.SourceGenerators.DependencyRegistration/Atc.SourceGenerators.DependencyRegistration.csproj" />
<Project Path="sample/Atc.SourceGenerators.EnumMapping/Atc.SourceGenerators.EnumMapping.csproj" />
Expand Down
98 changes: 97 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

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

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

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

Expand All @@ -20,6 +21,7 @@ src/
DependencyRegistrationGenerator.cs
OptionsBindingGenerator.cs
ObjectMappingGenerator.cs
AnnotationConstantsGenerator.cs
RuleIdentifierConstants.cs # Diagnostic ID constants
RuleCategoryConstants.cs # Diagnostic category constants
Atc.SourceGenerators.Annotations/ # Shared attribute definitions (published as separate package)
Expand All @@ -34,6 +36,7 @@ test/
DependencyRegistrationGeneratorTests.cs
OptionsBindingGeneratorTests.cs
ObjectMappingGeneratorTests.cs
AnnotationConstantsGeneratorTests.cs

sample/
Atc.SourceGenerators.DependencyRegistration/ # DI registration sample
Expand All @@ -43,6 +46,7 @@ sample/
Atc.SourceGenerators.Mapping/ # Object mapping API sample
Atc.SourceGenerators.Mapping.Domain/ # Domain models with mappings (includes BaseEntity/AuditableEntity/Book for inheritance demo)
Atc.SourceGenerators.Mapping.DataAccess/ # Database entities with mappings
Atc.SourceGenerators.AnnotationConstants/ # DataAnnotation constants sample
PetStore.Api/ # Complete 3-layer ASP.NET Core API with OpenAPI/Scalar
PetStore.Api.Contract/ # API contracts (DTOs)
PetStore.Domain/ # Domain layer using all generators
Expand Down Expand Up @@ -972,6 +976,98 @@ PetStatus (API)
- `ATCENUM001` - Target type must be an enum (Error)
- `ATCENUM002` - Source enum value has no matching target value (Warning)

### AnnotationConstantsGenerator

**Key Features:**
- Automatic scanning of classes/records with DataAnnotation attributes
- No opt-in attribute required - scans all types automatically
- Full support for 17 Microsoft DataAnnotation attributes
- **Atc attributes support** - IPAddress, Uri, String, KeyString, IsoCurrencySymbol, IgnoreDisplay, EnumGuid, CasingStyleDescription (when Atc package is referenced)
- Zero reflection at runtime - all metadata accessible at compile time
- Native AOT compatible
- Configurable via .editorconfig (`atc_annotation_constants.include_unannotated_properties`)

**Supported Microsoft DataAnnotation Attributes:**
- Display: DisplayName, Description, ShortName, GroupName, Prompt, Order
- Validation: Required (with AllowEmptyStrings, ErrorMessage), StringLength, MinLength, MaxLength, Range, RegularExpression
- Data Types: EmailAddress, Phone, Url, CreditCard, DataType
- Metadata: Key, Compare, Editable, ScaffoldColumn, Timestamp

**Supported Atc Attributes (from Atc package):**
- `IPAddressAttribute` - IsIPAddress, IPAddressRequired
- `IsoCurrencySymbolAttribute` - IsIsoCurrencySymbol, IsoCurrencySymbolRequired, AllowedIsoCurrencySymbols
- `StringAttribute` - IsAtcString, AtcStringRequired, AtcStringMinLength, AtcStringMaxLength, AtcStringRegularExpression, AtcStringInvalidCharacters, AtcStringInvalidPrefixStrings
- `KeyStringAttribute` - IsKeyString (plus all StringAttribute constants)
- `UriAttribute` - IsAtcUri, AtcUriRequired, AtcUriAllowHttp, AtcUriAllowHttps, AtcUriAllowFtp, AtcUriAllowFtps, AtcUriAllowFile, AtcUriAllowOpcTcp
- `IgnoreDisplayAttribute` - IsIgnoreDisplay
- `EnumGuidAttribute` - EnumGuid
- `CasingStyleDescriptionAttribute` - CasingStyleDefault, CasingStylePrefix

**Generated Code Pattern:**
```csharp
// Input:
using System.ComponentModel.DataAnnotations;

public class Product
{
[Display(Name = "Product Name", Description = "The display name of the product")]
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;

[Display(Name = "Price")]
[Required]
[Range(typeof(decimal), "0.01", "999999.99")]
public decimal Price { get; set; }
}

// Output (static partial class in same namespace):
namespace MyNamespace;

public static partial class AnnotationConstants
{
public static partial class Product
{
public static partial class Name
{
public const string DisplayName = "Product Name";
public const string Description = "The display name of the product";
public const bool IsRequired = true;
public const int MaximumLength = 100;
}

public static partial class Price
{
public const string DisplayName = "Price";
public const bool IsRequired = true;
public const string Minimum = "0.01";
public const string Maximum = "999999.99";
public static readonly System.Type OperandType = typeof(decimal);
}
}
}

// Usage:
string displayName = AnnotationConstants.Product.Name.DisplayName; // "Product Name"
bool isRequired = AnnotationConstants.Product.Name.IsRequired; // true
int maxLength = AnnotationConstants.Product.Name.MaximumLength; // 100
string priceMin = AnnotationConstants.Product.Price.Minimum; // "0.01"
Type priceType = AnnotationConstants.Product.Price.OperandType; // typeof(decimal)
```

**Configuration via .editorconfig:**
```ini
# Include properties without annotations (default: false)
atc_annotation_constants.include_unannotated_properties = true
```

**Use Cases:**
- Blazor dynamic forms without reflection
- Client-side validation metadata
- API documentation generation
- TypeScript type generation
- UI label/placeholder generation

## PetStore Sample - Complete Example

The `PetStore.Api` sample demonstrates all four generators working together in a realistic 3-layer ASP.NET Core application with OpenAPI/Scalar documentation.
Expand Down
112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ A collection of Roslyn C# source generators for .NET that eliminate boilerplate
- **[⚙️ OptionsBindingGenerator](#️-optionsbindinggenerator)** - Automatic configuration binding to strongly-typed options classes
- **[🗺️ MappingGenerator](#️-mappinggenerator)** - Automatic object-to-object mapping with type safety
- **[🔄 EnumMappingGenerator](#-enummappinggenerator)** - Automatic enum-to-enum mapping with intelligent matching
- **[📋 AnnotationConstantsGenerator](#-annotationconstantsgenerator)** - Compile-time access to DataAnnotation metadata

## ✨ See It In Action

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

---

### 📋 AnnotationConstantsGenerator

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.

#### 📚 Documentation

- **[Annotation Constants Guide](docs/AnnotationConstantsGenerator.md)** - Full documentation with examples
- **[Sample Projects](docs/AnnotationConstantsGenerator-Samples.md)** - Working code examples

#### 😫 From This

```csharp
// Runtime reflection to access annotation metadata 😫
var nameProperty = typeof(Product).GetProperty("Name");
var displayAttr = nameProperty.GetCustomAttribute<DisplayAttribute>();
var displayName = displayAttr?.Name; // "Product Name"

var maxLengthAttr = nameProperty.GetCustomAttribute<StringLengthAttribute>();
var maxLength = maxLengthAttr?.MaximumLength; // 100

// ... more reflection for each property
// ... slow, not AOT compatible
// ... no IntelliSense
```

#### ✨ To This

```csharp
// Your model - No special attribute needed ✨
using System.ComponentModel.DataAnnotations;

public class Product
{
[Display(Name = "Product Name", Description = "The product name")]
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;

[Display(Name = "Price")]
[Required]
[Range(typeof(decimal), "0.01", "999999.99")]
public decimal Price { get; set; }
}

// Access via generated constants - Zero reflection! ✨
string displayName = AnnotationConstants.Product.Name.DisplayName; // "Product Name"
string description = AnnotationConstants.Product.Name.Description; // "The product name"
bool isRequired = AnnotationConstants.Product.Name.IsRequired; // true
int maxLength = AnnotationConstants.Product.Name.MaximumLength; // 100

string priceMin = AnnotationConstants.Product.Price.Minimum; // "0.01"
string priceMax = AnnotationConstants.Product.Price.Maximum; // "999999.99"
Type priceType = AnnotationConstants.Product.Price.OperandType; // typeof(decimal)
```

#### ✨ Key Features

- **🔍 Automatic Scanning**: No opt-in attribute needed - scans all classes with DataAnnotation attributes
- **📦 Full DataAnnotations Support**: Display, Required, StringLength, Range, RegularExpression, EmailAddress, Phone, Url, CreditCard, DataType, Compare, Key, Editable, ScaffoldColumn, Timestamp
- **🔵 Atc Attributes Support**: IPAddress, Uri, String, KeyString, IsoCurrencySymbol, IgnoreDisplay, EnumGuid, CasingStyleDescription (when Atc package is referenced)
- **⚡ Zero Reflection**: All metadata accessible at compile time
- **🚀 Native AOT Compatible**: Fully trimming-safe, no runtime type inspection
- **🛡️ Type-Safe**: Full IntelliSense support with compile-time validation
- **⚙️ Configurable**: Optional `.editorconfig` setting to include unannotated properties
- **🎯 Perfect for Blazor**: Build dynamic forms without reflection
- **📝 API Documentation**: Generate OpenAPI schemas at compile time

#### 🚀 Quick Example

```csharp
using System.ComponentModel.DataAnnotations;

public class Customer
{
[Display(Name = "Full Name", Order = 1)]
[Required]
[StringLength(200)]
public string FullName { get; set; } = string.Empty;

[Display(Name = "Email Address", Order = 2)]
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;

[Key]
[Editable(false)]
public Guid Id { get; set; }
}

// ✨ Generated constants - use in validation, UI, docs
Console.WriteLine(AnnotationConstants.Customer.FullName.DisplayName); // "Full Name"
Console.WriteLine(AnnotationConstants.Customer.FullName.Order); // 1
Console.WriteLine(AnnotationConstants.Customer.FullName.IsRequired); // true
Console.WriteLine(AnnotationConstants.Customer.FullName.MaximumLength); // 200

Console.WriteLine(AnnotationConstants.Customer.Email.IsEmailAddress); // true
Console.WriteLine(AnnotationConstants.Customer.Id.IsKey); // true
Console.WriteLine(AnnotationConstants.Customer.Id.IsEditable); // false
```

---

## 🔨 Building

```bash
Expand Down Expand Up @@ -860,6 +963,15 @@ cd sample/Atc.SourceGenerators.EnumMapping
dotnet run
```

### 📋 [AnnotationConstants Sample](docs/AnnotationConstantsGenerator-Samples.md)

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.

```bash
cd sample/Atc.SourceGenerators.AnnotationConstants
dotnet run
```

### 🎯 [PetStore API - Complete Example](docs/PetStoreApi-Samples.md)

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