Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a67ec62
Add GeneratedCustomPropertyProviderAttribute class
Sergio0694 Dec 8, 2025
20fdb6c
Rename IBindableIReadOnlyListAdapter to BindableIReadOnlyListAdapter
Sergio0694 Dec 8, 2025
680fc09
Update marshaller attribute for BindableIReadOnlyListAdapter
Sergio0694 Dec 8, 2025
34df60e
Add SyntaxExtensions with helper methods for Roslyn
Sergio0694 Dec 8, 2025
705b700
Add extension for attribute analysis with config options
Sergio0694 Dec 8, 2025
dbdac77
Add MemberDeclarationSyntaxExtensions for partial checks
Sergio0694 Dec 8, 2025
9b519b5
WIP
Sergio0694 Dec 8, 2025
6f9452d
Add generic object pool implementation
Sergio0694 Dec 8, 2025
50f03fd
Add PooledArrayBuilder<T> helper for pooled arrays
Sergio0694 Dec 8, 2025
1018e04
Add IndentedTextWriter helper and update PooledArrayBuilder
Sergio0694 Dec 8, 2025
c260be4
Comment out bindable custom property generation logic
Sergio0694 Dec 8, 2025
5606365
Add IsDefaultOrEmpty and Length properties to EquatableArray
Sergio0694 Dec 8, 2025
a2f74ab
Add ITypeSymbol extension methods for metadata names
Sergio0694 Dec 8, 2025
e3d083c
Add IndentedTextWriter extension methods
Sergio0694 Dec 8, 2025
29307ed
Add HierarchyInfo and TypeInfo models
Sergio0694 Dec 8, 2025
8ff2957
Disallow ICustomPropertyProvider on ref types
Sergio0694 Dec 8, 2025
5c9f100
Add SkipNullValues extension for IncrementalValuesProvider
Sergio0694 Dec 9, 2025
a2b9b6f
Add EnumerateAllMembers extension for ITypeSymbol
Sergio0694 Dec 9, 2025
b62f462
Add 'this' modifier to SkipNullValues extension method
Sergio0694 Dec 9, 2025
5ea5d49
Add methods for fully qualified symbol names
Sergio0694 Dec 9, 2025
9c3eff8
WIP
Sergio0694 Dec 9, 2025
a538c4d
Refactor ToImmutable to use ToImmutableArray
Sergio0694 Dec 9, 2025
1a7bb59
Refactor GetCustomPropertyInfo for clarity and filtering
Sergio0694 Dec 9, 2025
0377e43
Refactor CustomPropertyProvider models and implementation
Sergio0694 Dec 9, 2025
c3c09e5
Refactor CustomPropertyProviderGenerator emit logic
Sergio0694 Dec 9, 2025
c445964
Add CanBeBoxed property to ITypeSymbolExtensions
Sergio0694 Dec 10, 2025
3b991cf
Add IsIndexer property to CustomPropertyInfo record
Sergio0694 Dec 10, 2025
c7a820f
Skip static indexer properties in generator
Sergio0694 Dec 10, 2025
4221aa3
Add code generation for ICustomProperty implementation types
Sergio0694 Dec 10, 2025
b287deb
Add ICustomPropertyProvider test and XAML references
Sergio0694 Dec 10, 2025
f130e14
Add diagnostic descriptors for custom property provider
Sergio0694 Dec 11, 2025
a27edf5
Add analyzer release tracking and test implementation
Sergio0694 Dec 11, 2025
10283ce
Add analyzer for GeneratedCustomPropertyProvider targets
Sergio0694 Dec 11, 2025
eee0dc0
Add analyzer for missing ICustomPropertyProvider interface
Sergio0694 Dec 11, 2025
391bd20
Add SourceGenerator2Test project to solution
Sergio0694 Dec 17, 2025
fde2769
Suppress CS8620 warning in generator file
Sergio0694 Dec 17, 2025
3656cdf
Set VersionOverride for CSharp.Workspaces package
Sergio0694 Dec 17, 2025
0a28fff
Add MSTest package to dependencies
Sergio0694 Dec 17, 2025
bacc98b
Add AssemblyInfo with Parallelize attribute to tests
Sergio0694 Dec 17, 2025
9f32e6e
Add CSharpGeneratorTest helper for source generator tests
Sergio0694 Dec 17, 2025
04a0907
Add test for CustomPropertyProviderGenerator
Sergio0694 Dec 17, 2025
86b35f9
Add custom CSharpAnalyzerTest helper for analyzer tests
Sergio0694 Dec 17, 2025
132e5af
Refactor RunGenerator parameter order and default
Sergio0694 Dec 17, 2025
416a7bf
Add tests for GeneratedCustomPropertyProvider analyzer
Sergio0694 Dec 17, 2025
da41df5
Add .NET 10 reference assemblies support for tests
Sergio0694 Dec 17, 2025
61d7794
Fix comment typo and rename test method
Sergio0694 Feb 9, 2026
08a15ed
Fix ICustomProperty emit for indexed/non-indexed
Sergio0694 Feb 9, 2026
4c6ae07
Use ComHelpers.EnsureQueryInterface in test
Sergio0694 Feb 9, 2026
7d7fbc8
Fix typos in XML/doc comments
Sergio0694 Feb 9, 2026
d27f0e0
Bump Roslyn packages to 5.0.0
Sergio0694 Feb 9, 2026
e655b49
Use IndentedTextWriter overload and clear buffer
Sergio0694 Feb 15, 2026
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
11 changes: 11 additions & 0 deletions src/Authoring/WinRT.SourceGenerator2/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

## Release 3.0.0

### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CSWINRT2000 | WindowsRuntime.SourceGenerator | Error | Invalid '[GeneratedCustomPropertyProvider]' target type
CSWINRT2001 | WindowsRuntime.SourceGenerator | Error | Missing 'partial' for '[GeneratedCustomPropertyProvider]' target type
CSWINRT2002 | WindowsRuntime.SourceGenerator | Error | 'ICustomPropertyProvider' interface type not available
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public static void EmitManagedExports(SourceProductionContext context, Authoring
return;
}

IndentedTextWriter writer = new(literalLength: 0, formattedCount: 0);
IndentedTextWriter writer = new(literalLength: 0, formattedCount: 0); // TODO: adjust the literal length

// Emit the '[WindowsRuntimeComponentAssemblyExportsType]' attribute so other tooling (including this same generator)
// can reliably find the generated export types from other assemblies, which is needed when merging activation factories.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using WindowsRuntime.SourceGenerator.Models;

#pragma warning disable CS8620, IDE0046 // TODO: remove 'CS8620' suppression when compiler warning is fixed

namespace WindowsRuntime.SourceGenerator;

/// <inheritdoc cref="CustomPropertyProviderGenerator"/>
public partial class CustomPropertyProviderGenerator
{
/// <summary>
/// Generation methods for <see cref="CustomPropertyProviderGenerator"/>.
/// </summary>
private static class Execute
{
/// <summary>
/// Checks whether a target node needs the <c>ICustomPropertyProvider</c> implementation.
/// </summary>
/// <param name="node">The target <see cref="SyntaxNode"/> instance to check.</param>
/// <param name="token">The cancellation token for the operation.</param>
/// <returns>Whether <paramref name="node"/> is a valid target for the <c>ICustomPropertyProvider</c> implementation.</returns>
[SuppressMessage("Style", "IDE0060", Justification = "The cancellation token is supplied by Roslyn.")]
public static bool IsTargetNodeValid(SyntaxNode node, CancellationToken token)
{
// We only care about class and struct types, all other types are not valid targets
if (!node.IsAnyKind(SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration))
{
return false;
}

// If the type is static, abstract, or 'ref', we cannot implement 'ICustomPropertyProvider' on it
if (((MemberDeclarationSyntax)node).Modifiers.ContainsAny(SyntaxKind.StaticKeyword, SyntaxKind.AbstractKeyword, SyntaxKind.RefKeyword))
{
return false;
}

// We can only generate the 'ICustomPropertyProvider' implementation if the type is 'partial'.
// Additionally, all parent type declarations must also be 'partial', for generation to work.
if (!((MemberDeclarationSyntax)node).IsPartialAndWithinPartialTypeHierarchy)
{
return false;
}

return true;
}

/// <summary>
/// Tries to get the <see cref="CustomPropertyProviderInfo"/> instance for a given annotated symbol.
/// </summary>
/// <param name="context">The <see cref="GeneratorAttributeSyntaxContextWithOptions"/> value to use.</param>
/// <param name="token">The cancellation token for the operation.</param>
/// <returns>The resulting <see cref="CustomPropertyProviderInfo"/> instance, if processed successfully.</returns>
public static CustomPropertyProviderInfo? GetCustomPropertyProviderInfo(GeneratorAttributeSyntaxContextWithOptions context, CancellationToken token)
{
bool useWindowsUIXamlProjections = context.GlobalOptions.GetBooleanProperty("CsWinRTUseWindowsUIXamlProjections");

token.ThrowIfCancellationRequested();

// Make sure that the target interface types are available. This is mostly because when UWP XAML projections
// are not used, the target project must be referencing the WinUI package to get the right interface type.
// If we can't find it, we just stop here. A separate diagnostic analyzer will emit the right diagnostic.
if ((useWindowsUIXamlProjections && context.SemanticModel.Compilation.GetTypeByMetadataName("Windows.UI.Xaml.Data.ICustomPropertyProvider") is null) ||
(!useWindowsUIXamlProjections && context.SemanticModel.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Data.ICustomPropertyProvider") is null))
{
return null;
}

token.ThrowIfCancellationRequested();

// Ensure we have a valid named type symbol for the annotated type
if (context.TargetSymbol is not INamedTypeSymbol typeSymbol)
{
return null;
}

// Get the type hierarchy (needed to correctly generate sources for nested types too)
HierarchyInfo typeHierarchy = HierarchyInfo.From(typeSymbol);

token.ThrowIfCancellationRequested();

// Gather all custom properties, depending on how the attribute was used
EquatableArray<CustomPropertyInfo> customProperties = GetCustomPropertyInfo(typeSymbol, context.Attributes[0], token);

token.ThrowIfCancellationRequested();

return new(
TypeHierarchy: typeHierarchy,
CustomProperties: customProperties,
UseWindowsUIXamlProjections: useWindowsUIXamlProjections);
}

/// <summary>
/// Gets the <see cref="CustomPropertyInfo"/> values for all applicable properties of a target type.
/// </summary>
/// <param name="typeSymbol">The annotated type.</param>
/// <param name="attribute">The attribute to trigger generation.</param>
/// <param name="token">The cancellation token for the operation.</param>
/// <returns>The resulting <see cref="CustomPropertyInfo"/> values for <paramref name="typeSymbol"/>.</returns>
private static EquatableArray<CustomPropertyInfo> GetCustomPropertyInfo(INamedTypeSymbol typeSymbol, AttributeData attribute, CancellationToken token)
{
string?[]? propertyNames = null;
ITypeSymbol?[]? indexerTypes = null;

token.ThrowIfCancellationRequested();

// If using the attribute constructor taking explicit property names and indexer
// types, get those names to filter the properties. We'll validate them later.
if (attribute.ConstructorArguments is [
{ Kind: TypedConstantKind.Array, Values: var typedPropertyNames },
{ Kind: TypedConstantKind.Array, Values: var typedIndexerTypes }])
{
propertyNames = [.. typedPropertyNames.Select(tc => tc.Value as string)];
indexerTypes = [.. typedIndexerTypes.Select(tc => tc.Value as ITypeSymbol)];
}

token.ThrowIfCancellationRequested();

using PooledArrayBuilder<CustomPropertyInfo> customPropertyInfo = new();

// Enumerate all members of the annotated type to discover all properties
foreach (ISymbol symbol in typeSymbol.EnumerateAllMembers())
{
token.ThrowIfCancellationRequested();

// Only gather public properties, and ignore overrides (we'll find the base definition instead).
// We also ignore partial property implementations, as we only care about the partial definitions.
if (symbol is not IPropertySymbol { DeclaredAccessibility: Accessibility.Public, IsOverride: false, PartialDefinitionPart: null } propertySymbol)
{
continue;
}

// Indexer properties must be instance properties
if (propertySymbol.IsIndexer && propertySymbol.IsStatic)
{
continue;
}

// We can only support indexers with a single parameter.
// If there's more, an analyzer will emit a warning.
if (propertySymbol.Parameters.Length > 1)
{
continue;
}

ITypeSymbol? indexerType = propertySymbol.Parameters.FirstOrDefault()?.Type;

// Ignore the current property if we have explicit filters and the property doesn't match
if ((propertySymbol.IsIndexer && indexerTypes?.Contains(indexerType, SymbolEqualityComparer.Default) is false) ||
(!propertySymbol.IsIndexer && propertyNames?.Contains(propertySymbol.Name, StringComparer.Ordinal) is false))
{
continue;
}

// If any types in the property signature cannot be boxed, we have to skip the property
if (!propertySymbol.Type.CanBeBoxed || indexerType?.CanBeBoxed is false)
{
continue;
}

// Gather all the info for the current property
customPropertyInfo.Add(new CustomPropertyInfo(
Name: propertySymbol.Name,
FullyQualifiedTypeName: propertySymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations(),
FullyQualifiedIndexerTypeName: indexerType?.GetFullyQualifiedNameWithNullabilityAnnotations(),
CanRead: propertySymbol.GetMethod is { DeclaredAccessibility: Accessibility.Public },
CanWrite: propertySymbol.SetMethod is { DeclaredAccessibility: Accessibility.Public },
IsStatic: propertySymbol.IsStatic));
}

token.ThrowIfCancellationRequested();

return customPropertyInfo.ToImmutable();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.CodeAnalysis;
using WindowsRuntime.SourceGenerator.Models;

namespace WindowsRuntime.SourceGenerator;

/// <summary>
/// A generator to emit <c>ICustomPropertyProvider</c> implementations for annotated types.
/// </summary>
[Generator]
public sealed partial class CustomPropertyProviderGenerator : IIncrementalGenerator
{
/// <inheritdoc/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Gather the info on all types annotated with '[GeneratedCustomPropertyProvider]'.
IncrementalValuesProvider<CustomPropertyProviderInfo> providerInfo = context.ForAttributeWithMetadataNameAndOptions(
fullyQualifiedMetadataName: "WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute",
predicate: Execute.IsTargetNodeValid,
transform: Execute.GetCustomPropertyProviderInfo)
.WithTrackingName("CustomPropertyProviderInfo")
.SkipNullValues();

// Write the implementation for all annotated types
context.RegisterSourceOutput(providerInfo, Emit.WriteCustomPropertyProviderImplementation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace WindowsRuntime.SourceGenerator.Diagnostics;

/// <summary>
/// A diagnostic analyzer that validates when <c>[GeneratedCustomPropertyProvider]</c> is used but no interface is available.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class GeneratedCustomPropertyProviderNoAvailableInterfaceTypeAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [DiagnosticDescriptors.GeneratedCustomPropertyProviderNoAvailableInterfaceType];

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(static context =>
{
// Get the '[GeneratedCustomPropertyProvider]' symbol
if (context.Compilation.GetTypeByMetadataName("WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute") is not { } attributeType)
{
return;
}

// Try to get any 'ICustomPropertyProvider' symbol
INamedTypeSymbol? windowsUIXamlCustomPropertyProviderType = context.Compilation.GetTypeByMetadataName("Windows.UI.Xaml.Data.ICustomPropertyProvider");
INamedTypeSymbol? microsoftUIXamlCustomPropertyProviderType = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Data.ICustomPropertyProvider");

// If we have either of them, we'll never need to report any diagnostics
if (windowsUIXamlCustomPropertyProviderType is not null || microsoftUIXamlCustomPropertyProviderType is not null)
{
return;
}

context.RegisterSymbolAction(context =>
{
// Only classes and structs can be targets of the attribute
if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } typeSymbol)
{
return;
}

// Emit a diagnostic if the type has the attribute, as it can't be used now
if (typeSymbol.HasAttributeWithType(attributeType))
{
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.GeneratedCustomPropertyProviderNoAvailableInterfaceType,
typeSymbol.Locations.FirstOrDefault(),
typeSymbol));
}
}, SymbolKind.NamedType);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace WindowsRuntime.SourceGenerator.Diagnostics;

/// <summary>
/// A diagnostic analyzer that validates target types for <c>[GeneratedCustomPropertyProvider]</c>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class GeneratedCustomPropertyProviderTargetTypeAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [
DiagnosticDescriptors.GeneratedCustomPropertyProviderInvalidTargetType,
DiagnosticDescriptors.GeneratedCustomPropertyProviderMissingPartialModifier];

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(static context =>
{
// Get the '[GeneratedCustomPropertyProvider]' symbol
if (context.Compilation.GetTypeByMetadataName("WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute") is not { } attributeType)
{
return;
}

context.RegisterSymbolAction(context =>
{
// Only classes and structs can be targets of the attribute
if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } typeSymbol)
{
return;
}

// Immediately bail if the type doesn't have the attribute
if (!typeSymbol.HasAttributeWithType(attributeType))
{
return;
}

// If the type is static, abstract, or 'ref', it isn't valid
if (typeSymbol.IsAbstract || typeSymbol.IsStatic || typeSymbol.IsRefLikeType)
{
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.GeneratedCustomPropertyProviderInvalidTargetType,
typeSymbol.Locations.FirstOrDefault(),
typeSymbol));
}

// Try to get a syntax reference for the symbol, to resolve the syntax node for it
if (typeSymbol.DeclaringSyntaxReferences.FirstOrDefault() is SyntaxReference syntaxReference)
{
SyntaxNode typeNode = syntaxReference.GetSyntax(context.CancellationToken);

// If there's no 'partial' modifier in the type hierarchy, the target type isn't valid
if (!((MemberDeclarationSyntax)typeNode).IsPartialAndWithinPartialTypeHierarchy)
{
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.GeneratedCustomPropertyProviderMissingPartialModifier,
typeSymbol.Locations.FirstOrDefault(),
typeSymbol));
}
}
}, SymbolKind.NamedType);
});
}
}
Loading
Loading