diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
index 5bc1b1792..c4cbb324e 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
@@ -55,6 +55,7 @@
+
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
index c6bf051a4..6aefd581a 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
@@ -16,6 +16,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
@@ -35,6 +36,7 @@ internal static class Execute
/// The instance to process.
/// The input instance to process.
/// The instance for the current run.
+ /// The options in use for the generator.
/// The cancellation token for the current operation.
/// The resulting value, if successfully retrieved.
/// The resulting diagnostics from the processing operation.
@@ -43,6 +45,7 @@ public static bool TryGetInfo(
FieldDeclarationSyntax fieldSyntax,
IFieldSymbol fieldSymbol,
SemanticModel semanticModel,
+ AnalyzerConfigOptions options,
CancellationToken token,
[NotNullWhen(true)] out PropertyInfo? propertyInfo,
out ImmutableArray diagnostics)
@@ -66,6 +69,11 @@ public static bool TryGetInfo(
token.ThrowIfCancellationRequested();
+ // Override the property changing support if explicitly disabled
+ shouldInvokeOnPropertyChanging &= GetEnableINotifyPropertyChangingSupport(options);
+
+ token.ThrowIfCancellationRequested();
+
// Get the property type and name
string typeNameWithNullabilityAnnotations = fieldSymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations();
string fieldName = fieldSymbol.Name;
@@ -320,6 +328,27 @@ 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/ComponentModel/ObservablePropertyGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs
index ad7e5fe03..68801d449 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs
@@ -25,8 +25,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Gather info for all annotated fields
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> propertyInfoWithErrors =
- context.SyntaxProvider
- .ForAttributeWithMetadataName(
+ context.ForAttributeWithMetadataNameAndOptions(
"CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute",
static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } },
static (context, token) =>
@@ -44,7 +43,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
token.ThrowIfCancellationRequested();
- _ = Execute.TryGetInfo(fieldDeclaration, fieldSymbol, context.SemanticModel, token, out PropertyInfo? propertyInfo, out ImmutableArray diagnostics);
+ _ = Execute.TryGetInfo(
+ fieldDeclaration,
+ fieldSymbol,
+ context.SemanticModel,
+ context.GlobalOptions,
+ token,
+ out PropertyInfo? propertyInfo,
+ out ImmutableArray diagnostics);
token.ThrowIfCancellationRequested();
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/GeneratorAttributeSyntaxContextWithOptions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/GeneratorAttributeSyntaxContextWithOptions.cs
new file mode 100644
index 000000000..2af137911
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/GeneratorAttributeSyntaxContextWithOptions.cs
@@ -0,0 +1,34 @@
+// 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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
+
+///
+///
+///
+/// The original value.
+/// The original value.
+internal readonly struct GeneratorAttributeSyntaxContextWithOptions(
+ GeneratorAttributeSyntaxContext syntaxContext,
+ AnalyzerConfigOptions globalOptions)
+{
+ ///
+ public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode;
+
+ ///
+ public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol;
+
+ ///
+ public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;
+
+ ///
+ public ImmutableArray Attributes { get; } = syntaxContext.Attributes;
+
+ ///
+ public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
+}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs
index d009810eb..1c627db6d 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs
@@ -4,7 +4,11 @@
using System;
using System.Collections.Immutable;
+using System.Threading;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+#pragma warning disable CS1574
namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
@@ -13,6 +17,31 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
///
internal static class IncrementalGeneratorInitializationContextExtensions
{
+ ///
+ public static IncrementalValuesProvider ForAttributeWithMetadataNameAndOptions(
+ this IncrementalGeneratorInitializationContext context,
+ string fullyQualifiedMetadataName,
+ Func predicate,
+ Func transform)
+ {
+ // Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
+ IncrementalValuesProvider syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName(
+ fullyQualifiedMetadataName,
+ predicate,
+ static (context, token) => context);
+
+ // Do the same for the analyzer config options
+ IncrementalValueProvider configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions);
+
+ // Merge the two and invoke the provided transform on these two values. Neither value
+ // is equatable, meaning the pipeline will always re-run until this point. This is
+ // intentional: we don't want any symbols or other expensive objects to be kept alive
+ // across incremental steps, especially if they could cause entire compilations to be
+ // rooted, which would significantly increase memory use and introduce more GC pauses.
+ // In this specific case, flowing non equatable values in a pipeline is therefore fine.
+ return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorAttributeSyntaxContextWithOptions(input.Left, input.Right), token));
+ }
+
///
/// Conditionally invokes
/// if the value produced by the input is .
diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets
index 19c3c56c0..bf6d6ed6c 100644
--- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets
+++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets
@@ -5,6 +5,14 @@
true
+
+
+
+
+