-
-
Notifications
You must be signed in to change notification settings - Fork 51
13_AnalyzerRules
Facet includes comprehensive Roslyn analyzers that provide real-time feedback in your IDE. These analyzers catch common mistakes and configuration issues at design-time, before you even compile your code.
| Rule ID | Severity | Category | Description |
|---|---|---|---|
| FAC001 | Error | Usage | Type must be annotated with [Facet] |
| FAC002 | Info | Performance | Consider using two-generic variant |
| FAC003 | Error | Declaration | Missing partial keyword on [Facet] type |
| FAC004 | Error | Usage | Invalid property name in Exclude/Include |
| FAC005 | Error | Usage | Invalid source type |
| FAC006 | Error | Usage | Invalid Configuration type |
| FAC007 | Warning | Usage | Invalid NestedFacets type |
| FAC008 | Warning | Performance | Circular reference risk |
| FAC009 | Error | Usage | Both Include and Exclude specified |
| FAC010 | Warning | Performance | Unusual MaxDepth value |
| FAC011 | Error | Usage | [GenerateDtos] on non-class type |
| FAC012 | Warning | Usage | Invalid ExcludeProperties |
| FAC013 | Warning | Usage | No DTO types selected |
| FAC014 | Error | Declaration | Missing partial keyword on [Flatten] type |
| FAC015 | Error | Usage | Invalid source type in [Flatten] |
| FAC016 | Warning | Performance | Unusual MaxDepth in [Flatten] |
| FAC017 | Info | Usage | LeafOnly naming collision risk |
| FAC022 | Warning | SourceTracking | Source entity structure changed |
Type must be annotated with [Facet]
- Severity: Error
- Category: Usage
When using extension methods like ToFacet<T>(), ToSource<T>(), SelectFacet<T>(), etc., the target type must be annotated with the [Facet] attribute.
// UserDto does NOT have [Facet] attribute
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
var dto = user.ToFacet<User, UserDto>(); // ❌ FAC001[Facet(typeof(User))]
public partial class UserDto { }
var dto = user.ToFacet<User, UserDto>(); // ✅ OKConsider using the two-generic variant for better performance
- Severity: Info
- Category: Performance
When using single-generic extension methods like ToFacet<TTarget>(), the library uses reflection to discover the source type. For better performance, use the two-generic variant ToFacet<TSource, TTarget>().
var dto = user.ToFacet<UserDto>(); // ℹ️ FAC002: Consider ToFacet<User, UserDto>()var dto = user.ToFacet<User, UserDto>(); // ✅ Better performanceThe performance difference is minimal (a few nanoseconds) but can add up in tight loops or high-throughput scenarios.
Type with [Facet] attribute must be declared as partial
- Severity: Error
- Category: Declaration
Source generators require types to be partial so they can add generated members. Any type marked with [Facet] must be declared as partial.
[Facet(typeof(User))]
public class UserDto { } // ❌ FAC003: Missing 'partial' keyword[Facet(typeof(User))]
public partial class UserDto { } // ✅ OKProperty name does not exist in source type
- Severity: Error
- Category: Usage
Property names specified in Exclude or Include parameters must exist in the source type.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
[Facet(typeof(User), "PasswordHash")] // ❌ FAC004: User doesn't have PasswordHash
public partial class UserDto { }public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string PasswordHash { get; set; }
}
[Facet(typeof(User), "PasswordHash")] // ✅ OK
public partial class UserDto { }Source type is not accessible or does not exist
- Severity: Error
- Category: Usage
The source type specified in the [Facet] attribute must be a valid, accessible type.
[Facet(typeof(NonExistentType))] // ❌ FAC005
public partial class UserDto { }[Facet(typeof(User))] // ✅ OK
public partial class UserDto { }Configuration type does not implement required interface
- Severity: Error
- Category: Usage
Configuration types must implement IFacetMapConfiguration<TSource, TTarget>, IFacetMapConfigurationAsync<TSource, TTarget>, IFacetProjectionMapConfiguration<TSource, TTarget>, or provide a static Map method.
public class UserMapper // ❌ No interface, no Map method
{
public void DoSomething(User source, UserDto target) { }
}
[Facet(typeof(User), Configuration = typeof(UserMapper))] // ❌ FAC006
public partial class UserDto { }// Option 1: Implement IFacetMapConfiguration
public class UserMapper : IFacetMapConfiguration<User, UserDto>
{
public static void Map(User source, UserDto target)
{
target.FullName = $"{source.FirstName} {source.LastName}";
}
}
// Option 2: Implement IFacetProjectionMapConfiguration (expression-only, reused in constructors)
public class UserMapper : IFacetProjectionMapConfiguration<User, UserDto>
{
public static void ConfigureProjection(IFacetProjectionBuilder<User, UserDto> builder)
{
builder.Map(d => d.FullName, s => s.FirstName + " " + s.LastName);
}
}
// Option 3: Provide static Map method
public class UserMapper
{
public static void Map(User source, UserDto target)
{
target.FullName = $"{source.FirstName} {source.LastName}";
}
}
[Facet(typeof(User), Configuration = typeof(UserMapper))] // ✅ OK
public partial class UserDto { }Nested facet type is not marked with [Facet] attribute
- Severity: Warning
- Category: Usage
All types specified in the NestedFacets array must be marked with the [Facet] attribute.
public class AddressDto { } // ❌ Missing [Facet] attribute
[Facet(typeof(User), NestedFacets = [typeof(AddressDto)])] // ⚠️ FAC007
public partial class UserDto { }[Facet(typeof(Address))]
public partial class AddressDto { }
[Facet(typeof(User), NestedFacets = [typeof(AddressDto)])] // ✅ OK
public partial class UserDto { }Potential stack overflow with circular references
- Severity: Warning
- Category: Performance
When MaxDepth is set to 0 (unlimited) and PreserveReferences is false, circular references in object graphs can cause stack overflow exceptions.
[Facet(typeof(User),
MaxDepth = 0,
PreserveReferences = false,
NestedFacets = [typeof(CompanyDto)])] // ⚠️ FAC008
public partial class UserDto { }// Option 1: Enable PreserveReferences (default)
[Facet(typeof(User),
NestedFacets = [typeof(CompanyDto)])] // ✅ OK (PreserveReferences defaults to true)
// Option 2: Set MaxDepth limit
[Facet(typeof(User),
MaxDepth = 5,
NestedFacets = [typeof(CompanyDto)])] // ✅ OK
// Option 3: Both
[Facet(typeof(User),
MaxDepth = 10,
PreserveReferences = true,
NestedFacets = [typeof(CompanyDto)])] // ✅ OK (safest)Cannot specify both Include and Exclude
- Severity: Error
- Category: Usage
The Include and Exclude parameters are mutually exclusive. Use either Include to whitelist properties or Exclude to blacklist properties, but not both.
[Facet(typeof(User),
nameof(User.PasswordHash), // Exclude parameter
Include = [nameof(User.Id), nameof(User.Name)])] // ❌ FAC009: Can't use both
public partial class UserDto { }// Option 1: Exclude approach
[Facet(typeof(User), nameof(User.PasswordHash), nameof(User.SecretKey))] // ✅ OK
public partial class UserDto { }
// Option 2: Include approach
[Facet(typeof(User), Include = [nameof(User.Id), nameof(User.Name), nameof(User.Email)])] // ✅ OK
public partial class UserDto { }MaxDepth value is unusual
- Severity: Warning
- Category: Performance
MaxDepth values should typically be between 1 and 10 for most scenarios. Negative values are invalid, and values above 100 may indicate a configuration error.
[Facet(typeof(User), MaxDepth = -1)] // ⚠️ FAC010: Negative
[Facet(typeof(User), MaxDepth = 500)] // ⚠️ FAC010: Too large[Facet(typeof(User), MaxDepth = 5)] // ✅ OK
[Facet(typeof(User), MaxDepth = 10)] // ✅ OK (default)[GenerateDtos] can only be applied to classes
- Severity: Error
- Category: Usage
The [GenerateDtos] and [GenerateAuditableDtos] attributes are designed for class types and cannot be applied to structs, interfaces, or other type kinds.
[GenerateDtos(DtoTypes.All)]
public struct Product { } // ❌ FAC011: Can't use on struct[GenerateDtos(DtoTypes.All)]
public class Product { } // ✅ OKExcluded property does not exist
- Severity: Warning
- Category: Usage
Properties specified in ExcludeProperties should exist in the source type.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
[GenerateDtos(DtoTypes.All,
ExcludeProperties = ["InternalNotes"])] // ⚠️ FAC012: Doesn't exist
public class Product { }public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string InternalNotes { get; set; }
}
[GenerateDtos(DtoTypes.All,
ExcludeProperties = ["InternalNotes"])] // ✅ OK
public class Product { }No DTO types selected for generation
- Severity: Warning
- Category: Usage
Setting Types to DtoTypes.None will not generate any DTOs.
[GenerateDtos(Types = DtoTypes.None)] // ⚠️ FAC013: No DTOs will be generated
public class Product { }[GenerateDtos(Types = DtoTypes.All)] // ✅ OK
public class Product { }
// Or specify specific types
[GenerateDtos(Types = DtoTypes.Create | DtoTypes.Update | DtoTypes.Response)]
public class Product { }Type with [Flatten] attribute must be declared as partial
- Severity: Error
- Category: Declaration
Similar to [Facet], types marked with [Flatten] must be partial.
[Flatten(typeof(Person))]
public class PersonFlat { } // ❌ FAC014[Flatten(typeof(Person))]
public partial class PersonFlat { } // ✅ OKSource type is not accessible or does not exist
- Severity: Error
- Category: Usage
The source type specified in the [Flatten] attribute must be valid and accessible.
[Flatten(typeof(NonExistentType))] // ❌ FAC015
public partial class PersonFlat { }[Flatten(typeof(Person))] // ✅ OK
public partial class PersonFlat { }MaxDepth value is unusual
- Severity: Warning
- Category: Performance
For flatten scenarios, MaxDepth values should typically be between 1 and 5. Values above 10 may cause excessive property generation.
[Flatten(typeof(Person), MaxDepth = -1)] // ⚠️ FAC016: Negative
[Flatten(typeof(Person), MaxDepth = 50)] // ⚠️ FAC016: Too large[Flatten(typeof(Person), MaxDepth = 3)] // ✅ OK (default)
[Flatten(typeof(Person), MaxDepth = 5)] // ✅ OKLeafOnly naming strategy may cause property name collisions
- Severity: Info
- Category: Usage
Using FlattenNamingStrategy.LeafOnly can cause name collisions when multiple nested objects have properties with the same name. Consider using the Prefix strategy instead.
[Flatten(typeof(Person),
NamingStrategy = FlattenNamingStrategy.LeafOnly)] // ℹ️ FAC017
public partial class PersonFlat { }public class Person
{
public Address HomeAddress { get; set; }
public Address WorkAddress { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
// With LeafOnly, both addresses map to "Street" and "City" → collision![Flatten(typeof(Person),
NamingStrategy = FlattenNamingStrategy.Prefix)] // ✅ Better
public partial class PersonFlat { }
// Generates: HomeAddressStreet, HomeAddressCity, WorkAddressStreet, WorkAddressCitySource entity structure changed
- Severity: Warning
- Category: SourceTracking
When you set SourceSignature on a [Facet] attribute, the analyzer computes a hash of the source type's properties and compares it to the stored signature. This warning is raised when the source entity's structure changes.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; } // New property added
}
[Facet(typeof(User), SourceSignature = "oldvalue")] // ⚠️ FAC022
public partial class UserDto { }Use the provided code fix to update the signature, or manually update it:
[Facet(typeof(User), SourceSignature = "newvalue")] // ✅ OK
public partial class UserDto { }- The signature is an 8-character hash computed from property names and types
- Respects
Include/Excludefilters when computing the signature - A code fix provider automatically offers to update the signature
- See Source Signature Change Tracking for details
If you need to suppress a specific analyzer rule, you can use:
#pragma warning disable FAC002
var dto = user.ToFacet<UserDto>();
#pragma warning restore FAC002[*.cs]
dotnet_diagnostic.FAC002.severity = none<PropertyGroup>
<NoWarn>$(NoWarn);FAC002</NoWarn>
</PropertyGroup>All analyzers are enabled by default. You can configure their severity in your .editorconfig file:
[*.cs]
# Set a rule to error
dotnet_diagnostic.FAC007.severity = error
# Set a rule to warning
dotnet_diagnostic.FAC002.severity = warning
# Disable a rule
dotnet_diagnostic.FAC017.severity = none