-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Context
The source generator's Parser (SourceGenerator.Parser.cs) and the IDE-time PropertyAnalyzer (PropertyAnalyzer.cs) both independently detect whether a property's type implements ISpanParsable<T> and ISpanFormattable. This interface detection logic is currently duplicated four times across the codebase:
- Parser, single properties (lines 257-259) — checks
typeSymbol.AllInterfacesforISpanParsableandISpanFormattablewhen processing a regularPropertyKeyPart. - Parser, repeating properties (lines 355-357) — performs the identical check on
innerTypeSymbol.AllInterfaceswhen processing aRepeatingPropertyKeyPart(the inner type extracted fromList<T>,IReadOnlyList<T>, etc.). - PropertyAnalyzer,
CreatePropertyTypeInfo(lines 318-320) — checksproperty.Type.AllInterfacesfor the same two interfaces. - PropertyAnalyzer,
CreateInnerTypeInfo(lines 291-293) — checks the inner type'sAllInterfacesfor generic collection properties.
Inconsistent display formats
The Parser and Analyzer use different SymbolDisplayFormat settings when comparing interface names:
- Parser uses
SymbolDisplayFormat.FullyQualifiedFormat, producing strings like"global::System.ISpanParsable<global::MyType>", then checks withStartsWith("global::System.ISpanParsable"). - PropertyAnalyzer uses the default display format (no explicit format argument), producing strings like
"System.ISpanParsable<MyType>", then checks withStartsWith("System.ISpanParsable", StringComparison.Ordinal).
Both approaches work correctly in practice (via StartsWith for ISpanParsable since it is generic, and Equals for ISpanFormattable), but the inconsistency is a maintenance hazard and makes it harder to reason about whether the analyzer diagnostics and the generator behaviour will always agree.
Why Analyzers.Common is the right home
Analyzers.Common (src/CompositeKey.Analyzers.Common/) already contains the shared PropertyValidation class with PropertyTypeInfo, ValidatePropertyFormat, ValidatePropertyTypeCompatibility, and GetFormattedLength. Both the SourceGeneration project and the Analyzers project reference Analyzers.Common:
CompositeKey.SourceGeneration.csprojhas a<ProjectReference>toCompositeKey.Analyzers.Common.CompositeKey.Analyzers.csprojalso references it.
Moving the interface detection into Analyzers.Common creates a single source of truth, ensuring that the analyzer's real-time diagnostics always match the generator's code emission decisions. If the detection logic ever needs to change (e.g., to support additional interfaces), it only needs to change in one place.
Relationship to #43
Issue #43 unifies the Parser's model types and merges its two separate type resolution code paths (for single and repeating properties) into one. After #43 lands, the Parser will have one copy of the interface detection code instead of two. However, it will still be duplicated between the Parser and the PropertyAnalyzer. This issue addresses that remaining duplication by extracting the shared logic into Analyzers.Common.
Blocked by
Tasks
Extract shared interface detection method into Analyzers.Common
Add a method to PropertyValidation (or a new utility class in Analyzers.Common) that accepts an ITypeSymbol and returns the IsSpanParsable and IsSpanFormattable booleans. The method signature should also accept any additional context needed for type comparisons (e.g., known type symbols for Guid, String) so that the full PropertyTypeInfo can be constructed in one place.
A possible signature:
public static PropertyTypeInfo CreatePropertyTypeInfo(
ITypeSymbol typeSymbol,
INamedTypeSymbol? guidType,
INamedTypeSymbol? stringType)This accepts the type symbol directly and the resolved well-known types needed for IsGuid/IsString checks, keeping Analyzers.Common free of any dependency on the Parser's KnownTypeSymbols class.
Resolve the display format inconsistency
When creating the shared method, the implementer needs to choose a consistent approach for interface detection. Options include:
- Standardise on one
SymbolDisplayFormat— pick eitherFullyQualifiedFormator the default, and use it consistently. - Avoid string comparison entirely — use
ITypeSymbol/INamedTypeSymbolequality or check the interface'sContainingNamespaceandMetadataNamedirectly, which is more robust than string matching.
Option 2 is preferred as it eliminates the format sensitivity altogether.
Update consumers
- Update the Parser (
SourceGenerator.Parser.cs) to call the shared method instead of inline interface detection. After refactor: Unify model layer and type resolution #43, this will be a single call site. - Update the PropertyAnalyzer's
CreatePropertyTypeInfoandCreateInnerTypeInfomethods to call the same shared method, replacing their inline detection code.
Verify
- All analyzer unit tests pass (
CompositeKey.Analyzers.UnitTests,CompositeKey.Analyzers.Common.UnitTests). - All source generation unit tests pass (
CompositeKey.SourceGeneration.UnitTests) — including snapshot tests. - All functional tests pass (
CompositeKey.SourceGeneration.FunctionalTests).