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
d118b23
Add GeneratedCustomPropertyProviderAttribute class
Sergio0694 Dec 8, 2025
68861bd
Rename IBindableIReadOnlyListAdapter to BindableIReadOnlyListAdapter
Sergio0694 Dec 8, 2025
4c4161e
Update marshaller attribute for BindableIReadOnlyListAdapter
Sergio0694 Dec 8, 2025
c4f9f47
Add SyntaxExtensions with helper methods for Roslyn
Sergio0694 Dec 8, 2025
9667ab9
Add extension for attribute analysis with config options
Sergio0694 Dec 8, 2025
7dd884f
Add MemberDeclarationSyntaxExtensions for partial checks
Sergio0694 Dec 8, 2025
d276814
WIP
Sergio0694 Dec 8, 2025
fff5a53
Add generic object pool implementation
Sergio0694 Dec 8, 2025
8ca59b8
Add PooledArrayBuilder<T> helper for pooled arrays
Sergio0694 Dec 8, 2025
1a0d9d8
Add IndentedTextWriter helper and update PooledArrayBuilder
Sergio0694 Dec 8, 2025
a4abc33
Comment out bindable custom property generation logic
Sergio0694 Dec 8, 2025
07e43be
Add IsDefaultOrEmpty and Length properties to EquatableArray
Sergio0694 Dec 8, 2025
3b3726b
Add ITypeSymbol extension methods for metadata names
Sergio0694 Dec 8, 2025
8b791bb
Add IndentedTextWriter extension methods
Sergio0694 Dec 8, 2025
6ac1a60
Add HierarchyInfo and TypeInfo models
Sergio0694 Dec 8, 2025
bbf8aa3
Disallow ICustomPropertyProvider on ref types
Sergio0694 Dec 8, 2025
f554a36
Add SkipNullValues extension for IncrementalValuesProvider
Sergio0694 Dec 9, 2025
39fae2b
Add EnumerateAllMembers extension for ITypeSymbol
Sergio0694 Dec 9, 2025
b711238
Add 'this' modifier to SkipNullValues extension method
Sergio0694 Dec 9, 2025
b865bcb
Add methods for fully qualified symbol names
Sergio0694 Dec 9, 2025
c19d854
WIP
Sergio0694 Dec 9, 2025
f3f476f
Refactor ToImmutable to use ToImmutableArray
Sergio0694 Dec 9, 2025
1706462
Refactor GetCustomPropertyInfo for clarity and filtering
Sergio0694 Dec 9, 2025
2e1b5c9
Refactor CustomPropertyProvider models and implementation
Sergio0694 Dec 9, 2025
b91cf21
Refactor CustomPropertyProviderGenerator emit logic
Sergio0694 Dec 9, 2025
7d6dc9f
Add CanBeBoxed property to ITypeSymbolExtensions
Sergio0694 Dec 10, 2025
e27b480
Add IsIndexer property to CustomPropertyInfo record
Sergio0694 Dec 10, 2025
1ad2123
Skip static indexer properties in generator
Sergio0694 Dec 10, 2025
a9a5012
Add code generation for ICustomProperty implementation types
Sergio0694 Dec 10, 2025
214cb07
Add ICustomPropertyProvider test and XAML references
Sergio0694 Dec 10, 2025
02a48d9
Add diagnostic descriptors for custom property provider
Sergio0694 Dec 11, 2025
3fcccee
Add analyzer release tracking and test implementation
Sergio0694 Dec 11, 2025
a554016
Add analyzer for GeneratedCustomPropertyProvider targets
Sergio0694 Dec 11, 2025
a19a815
Add analyzer for missing ICustomPropertyProvider interface
Sergio0694 Dec 11, 2025
633abc1
Add SourceGenerator2Test project to solution
Sergio0694 Dec 17, 2025
8609fa1
Suppress CS8620 warning in generator file
Sergio0694 Dec 17, 2025
f99ef1c
Set VersionOverride for CSharp.Workspaces package
Sergio0694 Dec 17, 2025
f7b0f7f
Add MSTest package to dependencies
Sergio0694 Dec 17, 2025
fea004c
Add AssemblyInfo with Parallelize attribute to tests
Sergio0694 Dec 17, 2025
71d2024
Add CSharpGeneratorTest helper for source generator tests
Sergio0694 Dec 17, 2025
c3cc9f8
Add test for CustomPropertyProviderGenerator
Sergio0694 Dec 17, 2025
29e772d
Add custom CSharpAnalyzerTest helper for analyzer tests
Sergio0694 Dec 17, 2025
40e25ce
Refactor RunGenerator parameter order and default
Sergio0694 Dec 17, 2025
b424003
Add tests for GeneratedCustomPropertyProvider analyzer
Sergio0694 Dec 17, 2025
8d8f967
Add .NET 10 reference assemblies support for tests
Sergio0694 Dec 17, 2025
2336d1d
Fix comment typo and rename test method
Sergio0694 Feb 9, 2026
e165448
Fix ICustomProperty emit for indexed/non-indexed
Sergio0694 Feb 9, 2026
ce419ef
Use ComHelpers.EnsureQueryInterface in test
Sergio0694 Feb 9, 2026
b56f84b
Fix typos in XML/doc comments
Sergio0694 Feb 9, 2026
7e275aa
Bump Roslyn packages to 5.0.0
Sergio0694 Feb 9, 2026
4aea9d1
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