diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs b/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs
index 403582ff0..43f5ca94c 100644
--- a/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs
+++ b/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs
@@ -57,7 +57,9 @@ public sealed class UsePartialPropertyForObservablePropertyCodeFixer : CodeFixPr
});
///
- public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(UseObservablePropertyOnPartialPropertyId);
+ public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(
+ UseObservablePropertyOnPartialPropertyId,
+ WinRTObservablePropertyOnFieldsIsNotAotCompatibleId);
///
public override FixAllProvider? GetFixAllProvider()
@@ -77,6 +79,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
return;
}
+ SemanticModel semanticModel = (await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false))!;
+
+ // If the language is not preview, we cannot apply this code fix (as it would generate invalid C# code)
+ if (!semanticModel.Compilation.IsLanguageVersionPreview())
+ {
+ return;
+ }
+
// Retrieve the properties passed by the analyzer
if (diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey] is not string fieldName ||
diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey] is not string propertyName)
@@ -101,7 +111,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
context.RegisterCodeFix(
CodeAction.Create(
title: "Use a partial property",
- createChangedDocument: token => ConvertToPartialProperty(context.Document, root, fieldDeclaration, fieldName, propertyName, context.CancellationToken),
+ createChangedDocument: token => ConvertToPartialProperty(context.Document, root, fieldDeclaration, semanticModel, fieldName, propertyName, context.CancellationToken),
equivalenceKey: "Use a partial property"),
diagnostic);
}
@@ -113,6 +123,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
/// The original document being fixed.
/// The original tree root belonging to the current document.
/// The for the field being updated.
+ /// The semantic model for .
/// The name of the annotated field.
/// The name of the generated property.
/// The cancellation token for the operation.
@@ -121,11 +132,12 @@ private static async Task ConvertToPartialProperty(
Document document,
SyntaxNode root,
FieldDeclarationSyntax fieldDeclaration,
+ SemanticModel semanticModel,
string fieldName,
string propertyName,
CancellationToken cancellationToken)
{
- SemanticModel semanticModel = (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false))!;
+ await Task.CompletedTask;
// Try to get all necessary type symbols to process the attributes
if (!semanticModel.Compilation.TryBuildNamedTypeSymbolMap(MvvmToolkitAttributeNamesToFullyQualifiedNamesMap, out ImmutableDictionary? toolkitTypeSymbols) ||
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md b/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md
index cb19f6246..6b2b3441d 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md
@@ -84,5 +84,9 @@ Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MVVMTK0041 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0041
MVVMTK0042 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Info | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0042
-MVVMTK0043| CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0043
-MVVMTK0044| CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0043
+MVVMTK0043 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0043
+MVVMTK0044 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0044
+MVVMTK0045 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0045
+MVVMTK0046 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0046
+MVVMTK0047 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0047
+MVVMTK0048 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0048
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
index 86d4254a3..11c513634 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
@@ -41,6 +41,9 @@
+
+
+
@@ -58,6 +61,7 @@
+
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
index 751c4bbc6..a0a05c742 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
@@ -153,8 +153,9 @@ public static bool TryGetInfo(
token.ThrowIfCancellationRequested();
- // Override the property changing support if explicitly disabled
- shouldInvokeOnPropertyChanging &= GetEnableINotifyPropertyChangingSupport(options);
+ // Override the property changing support if explicitly disabled.
+ // This setting is enabled by default, for backwards compatibility.
+ shouldInvokeOnPropertyChanging &= options.GetMSBuildBooleanPropertyValue("MvvmToolkitEnableINotifyPropertyChangingSupport", defaultValue: true);
token.ThrowIfCancellationRequested();
@@ -378,27 +379,6 @@ public static bool TryGetInfo(
return true;
}
- ///
- /// Gets the value for the "MvvmToolkitEnableINotifyPropertyChangingSupport" property.
- ///
- /// The options in use for the generator.
- /// The value for the "MvvmToolkitEnableINotifyPropertyChangingSupport" property.
- public static bool GetEnableINotifyPropertyChangingSupport(AnalyzerConfigOptions options)
- {
- if (options.TryGetValue("build_property.MvvmToolkitEnableINotifyPropertyChangingSupport", out string? propertyValue))
- {
- if (bool.TryParse(propertyValue, out bool enableINotifyPropertyChangingSupport))
- {
- return enableINotifyPropertyChangingSupport;
- }
- }
-
- // This setting is enabled by default, for backwards compatibility.
- // Note that this path should never be reached, as the default
- // value is also set in a .targets file bundled in the package.
- return true;
- }
-
///
/// Validates the containing type for a given field being annotated.
///
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs
index 68a77d6d1..caeeaaeff 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs
@@ -36,6 +36,12 @@ public override void Initialize(AnalysisContext context)
return;
}
+ // If CsWinRT is in AOT-optimization mode, disable this analyzer, as the WinRT one will produce a warning instead
+ if (context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.IsCsWinRTAotOptimizerEnabled(context.Compilation))
+ {
+ return;
+ }
+
// Get the symbol for [ObservableProperty]
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol)
{
@@ -73,7 +79,7 @@ public override void Initialize(AnalysisContext context)
.Add(FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey, fieldSymbol.Name)
.Add(FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey, ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol)),
fieldSymbol.ContainingType,
- fieldSymbol));
+ fieldSymbol.Name));
}, SymbolKind.Field);
});
}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs
new file mode 100644
index 000000000..af21f5d49
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs
@@ -0,0 +1,137 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if ROSLYN_4_11_0_OR_GREATER
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators;
+
+///
+/// A diagnostic analyzer that generates an error when [GeneratedBindableCustomProperty] is used on types with invalid generated base members.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(
+ WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField,
+ WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(static context =>
+ {
+ // This analyzer is only enabled when CsWinRT is also used
+ if (!context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.IsUsingWindowsRuntimePack())
+ {
+ return;
+ }
+
+ // Get the symbol for [ObservableProperty], [RelayCommand] and [GeneratedBindableCustomProperty]
+ if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol ||
+ context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute") is not INamedTypeSymbol relayCommandSymbol ||
+ context.Compilation.GetTypeByMetadataName("WinRT.GeneratedBindableCustomPropertyAttribute") is not INamedTypeSymbol generatedBindableCustomPropertySymbol)
+ {
+ return;
+ }
+
+ context.RegisterSymbolAction(context =>
+ {
+ // Ensure we do have a valid type
+ if (context.Symbol is not INamedTypeSymbol typeSymbol)
+ {
+ return;
+ }
+
+ // We only care about it if it's using [GeneratedBindableCustomProperty]
+ if (!typeSymbol.TryGetAttributeWithType(generatedBindableCustomPropertySymbol, out AttributeData? generatedBindableCustomPropertyAttribute))
+ {
+ return;
+ }
+
+ // Warn on all [ObservableProperty] fields
+ foreach (IFieldSymbol fieldSymbol in FindObservablePropertyFields(typeSymbol, observablePropertySymbol))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField,
+ generatedBindableCustomPropertyAttribute.GetLocation(),
+ typeSymbol,
+ fieldSymbol.ContainingType,
+ fieldSymbol.Name));
+ }
+
+ // Warn on all [RelayCommand] methods
+ foreach (IMethodSymbol methodSymbol in FindRelayCommandMethods(typeSymbol, relayCommandSymbol))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand,
+ generatedBindableCustomPropertyAttribute.GetLocation(),
+ typeSymbol,
+ methodSymbol));
+ }
+ }, SymbolKind.NamedType);
+ });
+ }
+
+ ///
+ /// Finds all methods in the base types that have the [RelayCommand] attribute.
+ ///
+ /// The instance to inspect.
+ /// The symbol for the [RelayCommand]
+ /// All instances for matching members.
+ private static IEnumerable FindRelayCommandMethods(INamedTypeSymbol typeSymbol, INamedTypeSymbol relayCommandSymbol)
+ {
+ // Check whether the base type (if any) is from the same assembly, and stop if it isn't. We do not
+ // want to include methods from the same type, as those will already be caught by another analyzer.
+ if (!SymbolEqualityComparer.Default.Equals(typeSymbol.ContainingAssembly, typeSymbol.BaseType?.ContainingAssembly))
+ {
+ yield break;
+ }
+
+ foreach (ISymbol memberSymbol in typeSymbol.BaseType.GetAllMembersFromSameAssembly())
+ {
+ if (memberSymbol is IMethodSymbol methodSymbol &&
+ methodSymbol.HasAttributeWithType(relayCommandSymbol))
+ {
+ yield return methodSymbol;
+ }
+ }
+ }
+
+ ///
+ /// Finds all fields in the base types that have the [ObservableProperty] attribute.
+ ///
+ /// The instance to inspect.
+ /// The symbol for the [ObservableProperty]
+ /// All instances for matching members.
+ private static IEnumerable FindObservablePropertyFields(INamedTypeSymbol typeSymbol, INamedTypeSymbol observablePropertySymbol)
+ {
+ // Skip the base type if not from the same assembly, same as above
+ if (!SymbolEqualityComparer.Default.Equals(typeSymbol.ContainingAssembly, typeSymbol.BaseType?.ContainingAssembly))
+ {
+ yield break;
+ }
+
+ foreach (ISymbol memberSymbol in typeSymbol.BaseType.GetAllMembersFromSameAssembly())
+ {
+ if (memberSymbol is IFieldSymbol fieldSymbol &&
+ fieldSymbol.HasAttributeWithType(observablePropertySymbol))
+ {
+ yield return fieldSymbol;
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs
new file mode 100644
index 000000000..7c619c120
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if ROSLYN_4_11_0_OR_GREATER
+
+using System.Collections.Immutable;
+using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators;
+
+///
+/// A diagnostic analyzer that generates an error when [ObservableProperty] is used on a field in a scenario where it wouldn't be AOT compatible.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(WinRTObservablePropertyOnFieldsIsNotAotCompatible);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(static context =>
+ {
+ // This analyzer is only enabled in cases where CsWinRT is producing AOT-compatible code
+ if (!context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.IsCsWinRTAotOptimizerEnabled(context.Compilation))
+ {
+ return;
+ }
+
+ // Get the symbol for [ObservableProperty]
+ if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol)
+ {
+ return;
+ }
+
+ context.RegisterSymbolAction(context =>
+ {
+ // Ensure we do have a valid field
+ if (context.Symbol is not IFieldSymbol fieldSymbol)
+ {
+ return;
+ }
+
+ // Emit a diagnostic if the field is using the [ObservableProperty] attribute
+ if (fieldSymbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ WinRTObservablePropertyOnFieldsIsNotAotCompatible,
+ observablePropertyAttribute.GetLocation(),
+ ImmutableDictionary.Create()
+ .Add(FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey, fieldSymbol.Name)
+ .Add(FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey, ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol)),
+ fieldSymbol.ContainingType,
+ fieldSymbol.Name));
+ }
+ }, SymbolKind.Field);
+ });
+ }
+}
+
+#endif
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs
new file mode 100644
index 000000000..13dd28e73
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators;
+
+///
+/// A diagnostic analyzer that generates an error when [RelayCommand] is used on a method inside a type with [GeneratedBindableCustomProperty].
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatible);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(static context =>
+ {
+ // This analyzer is only enabled when CsWinRT is also used
+ if (!context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.IsUsingWindowsRuntimePack())
+ {
+ return;
+ }
+
+ // Get the symbol for [RelayCommand] and [GeneratedBindableCustomProperty]
+ if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute") is not INamedTypeSymbol relayCommandSymbol ||
+ context.Compilation.GetTypeByMetadataName("WinRT.GeneratedBindableCustomPropertyAttribute") is not INamedTypeSymbol generatedBindableCustomPropertySymbol)
+ {
+ return;
+ }
+
+ context.RegisterSymbolAction(context =>
+ {
+ // Ensure we do have a valid method with a containing type we can reference
+ if (context.Symbol is not IMethodSymbol { ContainingType: INamedTypeSymbol typeSymbol } methodSymbol)
+ {
+ return;
+ }
+
+ // If the method is not using [RelayCommand], we can skip it
+ if (!methodSymbol.TryGetAttributeWithType(relayCommandSymbol, out AttributeData? relayCommandAttribute))
+ {
+ return;
+ }
+
+ // If the containing type is using [GeneratedBindableCustomProperty], emit a warning
+ if (typeSymbol.HasAttributeWithType(generatedBindableCustomPropertySymbol))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatible,
+ relayCommandAttribute.GetLocation(),
+ methodSymbol));
+ }
+ }, SymbolKind.Method);
+ });
+ }
+}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
index 6ac4c7950..13e4d7e65 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
@@ -39,6 +39,11 @@ internal static class DiagnosticDescriptors
///
public const string UseObservablePropertyOnPartialPropertyId = "MVVMTK0042";
+ ///
+ /// The diagnostic id for .
+ ///
+ public const string WinRTObservablePropertyOnFieldsIsNotAotCompatibleId = "MVVMTK0045";
+
///
/// Gets a indicating when a duplicate declaration of would happen.
///
@@ -681,7 +686,7 @@ internal static class DiagnosticDescriptors
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0040");
///
- /// Gets a for a CanvasEffect property with invalid accessors.
+ /// Gets a for the C# language version not being sufficient for [ObservableProperty] on partial properties.
///
/// Format: "Using [ObservableProperty] on partial properties requires the C# language version to be set to 'preview', as support for the 'field' keyword is needed by the source generators to emit valid code (add preview to your .csproj/.props file)".
///
@@ -697,7 +702,7 @@ internal static class DiagnosticDescriptors
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0041");
///
- /// Gets a for a CanvasEffect property with invalid accessors.
+ /// Gets a for when [ObservableProperty] on a field should be converted to a partial property.
///
/// Format: "The field {0}.{1} using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)".
///
@@ -743,4 +748,68 @@ internal static class DiagnosticDescriptors
isEnabledByDefault: true,
description: "Using [ObservableProperty] with (partial) properties requires a higher version of Roslyn (remove [ObservableProperty] or target a field instead, or upgrade to at least Visual Studio 2022 version 17.12 and the .NET 9 SDK).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0044");
+
+ ///
+ /// Gets a for when [ObservableProperty] is used on a field in WinRT scenarios.
+ ///
+ /// Format: "The field {0}.{1} using [ObservableProperty] will generate code that is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and a partial property should be used instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code)".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor WinRTObservablePropertyOnFieldsIsNotAotCompatible = new(
+ id: WinRTObservablePropertyOnFieldsIsNotAotCompatibleId,
+ title: "Using [ObservableProperty] on fields is not AOT compatible for WinRT",
+ messageFormat: """The field {0}.{1} using [ObservableProperty] will generate code that is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and a partial property should be used instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code)""",
+ category: typeof(ObservablePropertyGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Fields using [ObservableProperty] will generate code that is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and partial properties should be used instead (as they allow the CsWinRT generators to correctly produce the necessary WinRT marshalling code).",
+ helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0045");
+
+ ///
+ /// Gets a for when [RelayCommand] is used on a method in types where [GeneratedBindableCustomProperty] is used.
+ ///
+ /// Format: "The method {0} using [RelayCommand] within a type also using [GeneratedBindableCustomProperty], which is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator)".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatible = new(
+ id: "MVVMTK0046",
+ title: "Using [RelayCommand] is not compatible with [GeneratedBindableCustomProperty]",
+ messageFormat: """The method {0} using [RelayCommand] within a type also using [GeneratedBindableCustomProperty], which is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator)""",
+ category: typeof(RelayCommandGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Using [RelayCommand] on methods within a type also using [GeneratedBindableCustomProperty] is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator).",
+ helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0046");
+
+ ///
+ /// Gets a for when [GeneratedBindableCustomProperty] is used on a type that also uses [ObservableProperty] on any declared or inherited fields.
+ ///
+ /// Format: "The type {0} using [GeneratedBindableCustomProperty] is also using [ObservableProperty] on its declared (or inherited) field {1}.{2}: combining the two generators is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField = new(
+ id: "MVVMTK0047",
+ title: "Using [GeneratedBindableCustomProperty] is not compatible with [ObservableProperty] on fields",
+ messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its declared (or inherited) method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""",
+ category: typeof(ObservablePropertyGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Using [GeneratedBindableCustomProperty] on types that also use [ObservableProperty] on any declared (or inherited) fields is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).",
+ helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0047");
+
+ ///
+ /// Gets a for when [GeneratedBindableCustomProperty] is used on a type that also uses [RelayCommand] on any declared or inherited methods.
+ ///
+ /// Format: "The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand = new(
+ id: "MVVMTK0048",
+ title: "Using [GeneratedBindableCustomProperty] is not compatible with [RelayCommand]",
+ messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""",
+ category: typeof(RelayCommandGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Using [GeneratedBindableCustomProperty] on types that also use [RelayCommand] on any inherited methods is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).",
+ helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0048");
}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/AnalyzerConfigOptionsExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/AnalyzerConfigOptionsExtensions.cs
new file mode 100644
index 000000000..7556bd45a
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/AnalyzerConfigOptionsExtensions.cs
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
+
+///
+/// Extension methods for the type.
+///
+internal static class AnalyzerConfigOptionsExtensions
+{
+ ///
+ /// Checks whether the Windows runtime pack is being used (ie. if the target framework is net8.0-windows10.0.17763.0 or above).
+ ///
+ /// The input instance.
+ /// Whether the Windows runtime pack is being used.
+ public static bool IsUsingWindowsRuntimePack(this AnalyzerConfigOptions options)
+ {
+ return options.GetMSBuildBooleanPropertyValue("_MvvmToolkitIsUsingWindowsRuntimePack");
+ }
+
+ ///
+ /// Checks whether CsWinRT is configured in AOT support mode.
+ ///
+ /// The input instance.
+ /// The input instance in use.
+ /// Whether CsWinRT is configured in AOT support mode.
+ public static bool IsCsWinRTAotOptimizerEnabled(this AnalyzerConfigOptions options, Compilation compilation)
+ {
+ // If the runtime pack isn't being used, CsWinRT won't be used either. Technically speaking it's possible
+ // to reference CsWinRT without targeting Windows, but that's not a scenario that is supported anyway.
+ if (!options.IsUsingWindowsRuntimePack())
+ {
+ return false;
+ }
+
+ if (options.TryGetMSBuildStringPropertyValue("CsWinRTAotOptimizerEnabled", out string? csWinRTAotOptimizerEnabled))
+ {
+ // If the generators are in opt-in mode, we will not show warnings
+ if (string.Equals(csWinRTAotOptimizerEnabled, "OptIn", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // The automatic mode will generate marshalling code for all possible scenarios
+ if (string.Equals(csWinRTAotOptimizerEnabled, "Auto", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ // The default value of "true" will run in automatic mode for some scenarios, which we have to check
+ if (bool.TryParse(csWinRTAotOptimizerEnabled, out bool isCsWinRTAotOptimizerEnabled) && isCsWinRTAotOptimizerEnabled)
+ {
+ // The CsWinRT generator will be enabled for AOT scenarios in the following cases:
+ // - The project is producing a WinRT component
+ // - The 'CsWinRTAotWarningLevel' is set to '2', ie. all marshalling code even for built-in types should be produced
+ // - The app is either UWP XAML or WinUI 3 (which is detected by the presence of the 'Button' type
+ // For additional reference, see the source code at https://github.com/microsoft/CsWinRT.
+ return
+ options.GetMSBuildBooleanPropertyValue("CsWinRTComponent") ||
+ options.GetMSBuildInt32PropertyValue("CsWinRTAotWarningLevel") == 2 ||
+ compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Controls.Button") is not null ||
+ compilation.GetTypeByMetadataName("Windows.UI.Xaml.Controls.Button") is not null;
+
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the boolean value of a given MSBuild property from an input instance.
+ ///
+ /// The input instance.
+ /// The name of the target MSBuild property.
+ /// The default value to return if the property is not found or cannot be parsed.
+ /// The value of the target MSBuild property.
+ public static bool GetMSBuildBooleanPropertyValue(this AnalyzerConfigOptions options, string propertyName, bool defaultValue = false)
+ {
+ if (options.TryGetMSBuildStringPropertyValue(propertyName, out string? propertyValue))
+ {
+ if (bool.TryParse(propertyValue, out bool booleanPropertyValue))
+ {
+ return booleanPropertyValue;
+ }
+ }
+
+ return defaultValue;
+ }
+
+ ///
+ /// Gets the integer value of a given MSBuild property from an input instance.
+ ///
+ /// The input instance.
+ /// The name of the target MSBuild property.
+ /// The default value to return if the property is not found or cannot be parsed.
+ /// The value of the target MSBuild property.
+ public static int GetMSBuildInt32PropertyValue(this AnalyzerConfigOptions options, string propertyName, int defaultValue = 0)
+ {
+ if (options.TryGetMSBuildStringPropertyValue(propertyName, out string? propertyValue))
+ {
+ if (int.TryParse(propertyValue, out int int32PropertyValue))
+ {
+ return int32PropertyValue;
+ }
+ }
+
+ return defaultValue;
+ }
+
+ ///
+ /// Tries to get a value of a given MSBuild property from an input instance.
+ ///
+ /// The input instance.
+ /// The name of the target MSBuild property.
+ /// The resulting property value.
+ /// Whether the property value was retrieved..
+ public static bool TryGetMSBuildStringPropertyValue(this AnalyzerConfigOptions options, string propertyName, [NotNullWhen(true)] out string? propertyValue)
+ {
+ return options.TryGetValue($"build_property.{propertyName}", out propertyValue);
+ }
+}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs
index 8b847bcf0..367b16f78 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs
@@ -28,6 +28,28 @@ public static IEnumerable GetAllMembers(this INamedTypeSymbol symbol)
}
}
+ ///
+ /// Gets all member symbols from a given instance, including inherited ones, only if they are declared in source.
+ ///
+ /// The input instance.
+ /// A sequence of all member symbols for .
+ public static IEnumerable GetAllMembersFromSameAssembly(this INamedTypeSymbol symbol)
+ {
+ for (INamedTypeSymbol? currentSymbol = symbol; currentSymbol is { SpecialType: not SpecialType.System_Object }; currentSymbol = currentSymbol.BaseType)
+ {
+ // Stop early when we reach a base type from another assembly
+ if (!SymbolEqualityComparer.Default.Equals(currentSymbol.ContainingAssembly, symbol.ContainingAssembly))
+ {
+ yield break;
+ }
+
+ foreach (ISymbol memberSymbol in currentSymbol.GetMembers())
+ {
+ yield return memberSymbol;
+ }
+ }
+ }
+
///
/// Gets all member symbols from a given instance, including inherited ones.
///
diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.Windows.targets b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.Windows.targets
new file mode 100644
index 000000000..3602b1438
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.Windows.targets
@@ -0,0 +1,17 @@
+
+
+
+
+ <_MvvmToolkitIsUsingWindowsRuntimePack>false
+ <_MvvmToolkitIsUsingWindowsRuntimePack Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0-windows10.0.17763.0'))">true
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
index 44b02cfa8..cddfad4af 100644
--- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
+++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
@@ -101,10 +101,12 @@
+
+
diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets
index 9006210d8..394a0a111 100644
--- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets
+++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.targets
@@ -4,12 +4,14 @@
<_CommunityToolkitMvvmFeatureSwitchesTargets>$(MSBuildThisFileDirectory)CommunityToolkit.Mvvm.FeatureSwitches.targets
<_CommunityToolkitMvvmSourceGeneratorsTargets>$(MSBuildThisFileDirectory)CommunityToolkit.Mvvm.SourceGenerators.targets
+ <_CommunityToolkitMvvmWindowsTargets>$(MSBuildThisFileDirectory)CommunityToolkit.Mvvm.Windows.targets
<_CommunityToolkitMvvmWindowsSdkTargets>$(MSBuildThisFileDirectory)CommunityToolkit.Mvvm.WindowsSdk.targets
+
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs
index e38182eaf..d7fdfc933 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -271,4 +272,460 @@ public partial class SampleViewModel : ObservableObject
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview);
}
+
+ [TestMethod]
+ public async Task UseObservablePropertyOnPartialPropertyAnalyzer_CsWinRTAotOptimizerEnabled_Auto_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private static string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_NotTargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: []);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_OptIn_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_False_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "false")]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_Auto_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0045:ObservableProperty|}]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_NoXaml_Level1_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_NoXaml_Level2_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0045:ObservableProperty|}]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 2)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_NoXaml_1_Component_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0045:ObservableProperty|}]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1), ("CsWinRTComponent", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_UwpXaml_Level1_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0045:ObservableProperty|}]
+ private string name;
+ }
+ }
+
+ namespace Windows.UI.Xaml.Controls
+ {
+ public class Button;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_WinUIXaml_Level1_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0045:ObservableProperty|}]
+ private string name;
+ }
+ }
+
+ namespace Microsoft.UI.Xaml.Controls
+ {
+ public class Button;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_NotTargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: []);
+ }
+
+ [TestMethod]
+ public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_Bindable_Warns()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [GeneratedBindableCustomProperty]
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0046:RelayCommand|}]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_NotTargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [GeneratedBindableCustomProperty]
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: []);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [GeneratedBindableCustomProperty]
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Field_Warns()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [{|MVVMTK0047:GeneratedBindableCustomProperty|}]
+ public partial class SampleViewModel : BaseViewModel
+ {
+ }
+
+ public partial class BaseViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Method_Warns()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [{|MVVMTK0048:GeneratedBindableCustomProperty|}]
+ public partial class SampleViewModel : BaseViewModel
+ {
+ }
+
+ public partial class BaseViewModel : ObservableObject
+ {
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs
index c111ef8de..1236d5971 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs
@@ -54,8 +54,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(5,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -102,8 +102,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(5,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -152,8 +152,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(5,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -204,8 +204,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(6,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(6,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -275,8 +275,8 @@ public class TestAttribute(string text) : Attribute;
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(7,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 6, 7, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(7,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 6, 7, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -323,8 +323,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(6,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(6,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -373,8 +373,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(7,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 6, 7, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(7,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 6, 7, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -427,8 +427,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(9,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(9, 6, 9, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(9,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(9, 6, 9, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -487,8 +487,8 @@ public void M()
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(5,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -533,8 +533,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(5,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -584,8 +584,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(6,6): info MVVMTK0042: The field C.C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "C.items"),
+ // /0/Test0.cs(6,6): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "items"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -635,8 +635,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(6,6): info MVVMTK0042: The field C.C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "C.items"),
+ // /0/Test0.cs(6,6): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "items"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -685,8 +685,8 @@ partial class C : ObservableObject
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(5,6): info MVVMTK0042: The field C.C.foo using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "C.foo"),
+ // /0/Test0.cs(5,6): info MVVMTK0042: The field C.foo using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "foo"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
@@ -755,8 +755,8 @@ public void M()
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
test.ExpectedDiagnostics.AddRange(new[]
{
- // /0/Test0.cs(5,6): info MVVMTK0042: The field C.C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
- CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "C.i"),
+ // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"),
});
test.FixedState.ExpectedDiagnostics.AddRange(new[]
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs
index 8e41258ab..c44dbc154 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs
@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -10,6 +13,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
#if NET472
using System.ComponentModel.DataAnnotations;
#endif
@@ -59,8 +63,52 @@ public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVe
#endif
test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ObservableObject).Assembly.Location));
+ test.SolutionTransforms.Add((solution, projectId) =>
+ solution.AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId),
+ "UseMarshalType.editorconfig",
+ SourceText.From("""
+ is_global = true
+ build_property.LibraryImportGenerator_UseMarshalType = true
+ """,
+ Encoding.UTF8),
+ filePath: "/UseMarshalType.editorconfig"));
+
test.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync(CancellationToken.None);
}
+
+ ///
+ /// The language version to use to run the test.
+ public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion, (string PropertyName, object PropertyValue)[] editorconfig)
+ {
+ CSharpAnalyzerWithLanguageVersionTest test = new(languageVersion) { TestCode = source };
+
+#if NET8_0_OR_GREATER
+ test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net80;
+#elif NET6_0_OR_GREATER
+ test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
+#else
+ test.TestState.ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default;
+ test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(RequiredAttribute).Assembly.Location));
+#endif
+ test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ObservableObject).Assembly.Location));
+
+ // Add any editorconfig properties, if present
+ if (editorconfig.Length > 0)
+ {
+ test.SolutionTransforms.Add((solution, projectId) =>
+ solution.AddAnalyzerConfigDocument(
+ DocumentId.CreateNewId(projectId),
+ "MvvmToolkitAnalyzers.editorconfig",
+ SourceText.From($"""
+ is_global = true
+ {string.Join(Environment.NewLine, editorconfig.Select(static p => $"build_property.{p.PropertyName} = {p.PropertyValue}"))}
+ """,
+ Encoding.UTF8),
+ filePath: "/MvvmToolkitAnalyzers.editorconfig"));
+ }
+
+ return test.RunAsync(CancellationToken.None);
+ }
}