Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\CompilationExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\DiagnosticsExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\INamedTypeSymbolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\GeneratorAttributeSyntaxContextWithOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\IncrementalGeneratorInitializationContextExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\IncrementalValuesProviderExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\MethodDeclarationSyntaxExtensions.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -35,6 +36,7 @@ internal static class Execute
/// <param name="fieldSyntax">The <see cref="FieldDeclarationSyntax"/> instance to process.</param>
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
/// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
/// <param name="options">The options in use for the generator.</param>
/// <param name="token">The cancellation token for the current operation.</param>
/// <param name="propertyInfo">The resulting <see cref="PropertyInfo"/> value, if successfully retrieved.</param>
/// <param name="diagnostics">The resulting diagnostics from the processing operation.</param>
Expand All @@ -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<DiagnosticInfo> diagnostics)
Expand All @@ -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;
Expand Down Expand Up @@ -320,6 +328,27 @@ public static bool TryGetInfo(
return true;
}

/// <summary>
/// Gets the value for the "MvvmToolkitEnableINotifyPropertyChangingSupport" property.
/// </summary>
/// <param name="options">The options in use for the generator.</param>
/// <returns>The value for the "MvvmToolkitEnableINotifyPropertyChangingSupport" property.</returns>
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;
}

/// <summary>
/// Validates the containing type for a given field being annotated.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Gather info for all annotated fields
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result<PropertyInfo?> 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) =>
Expand All @@ -44,7 +43,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

token.ThrowIfCancellationRequested();

_ = Execute.TryGetInfo(fieldDeclaration, fieldSymbol, context.SemanticModel, token, out PropertyInfo? propertyInfo, out ImmutableArray<DiagnosticInfo> diagnostics);
_ = Execute.TryGetInfo(
fieldDeclaration,
fieldSymbol,
context.SemanticModel,
context.GlobalOptions,
token,
out PropertyInfo? propertyInfo,
out ImmutableArray<DiagnosticInfo> diagnostics);

token.ThrowIfCancellationRequested();

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// <inheritdoc cref="GeneratorAttributeSyntaxContext" path="/summary/node()"/>
/// </summary>
/// <param name="syntaxContext">The original <see cref="GeneratorAttributeSyntaxContext"/> value.</param>
/// <param name="globalOptions">The original <see cref="AnalyzerConfigOptions"/> value.</param>
internal readonly struct GeneratorAttributeSyntaxContextWithOptions(
GeneratorAttributeSyntaxContext syntaxContext,
AnalyzerConfigOptions globalOptions)
{
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetNode"/>
public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode;

/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetSymbol"/>
public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol;

/// <inheritdoc cref="GeneratorAttributeSyntaxContext.SemanticModel"/>
public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;

/// <inheritdoc cref="GeneratorAttributeSyntaxContext.Attributes"/>
public ImmutableArray<AttributeData> Attributes { get; } = syntaxContext.Attributes;

/// <inheritdoc cref="AnalyzerConfigOptionsProvider.GlobalOptions"/>
public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -13,6 +17,31 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
/// </summary>
internal static class IncrementalGeneratorInitializationContextExtensions
{
/// <inheritdoc cref="SyntaxValueProvider.ForAttributeWithMetadataName"/>
public static IncrementalValuesProvider<T> ForAttributeWithMetadataNameAndOptions<T>(
this IncrementalGeneratorInitializationContext context,
string fullyQualifiedMetadataName,
Func<SyntaxNode, CancellationToken, bool> predicate,
Func<GeneratorAttributeSyntaxContextWithOptions, CancellationToken, T> transform)
{
// Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
IncrementalValuesProvider<GeneratorAttributeSyntaxContext> syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName(
fullyQualifiedMetadataName,
predicate,
static (context, token) => context);

// Do the same for the analyzer config options
IncrementalValueProvider<AnalyzerConfigOptions> 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));
}

/// <summary>
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterSourceOutput{TSource}(IncrementalValueProvider{TSource}, Action{SourceProductionContext, TSource})"/>
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
<MvvmToolkitEnableINotifyPropertyChangingSupport Condition="'$(MvvmToolkitEnableINotifyPropertyChangingSupport)' == ''">true</MvvmToolkitEnableINotifyPropertyChangingSupport>
</PropertyGroup>

<!--
Make the properties visible to the source generators as well, to allow
them to emit optimized codegen ahead of time depending on their values.
-->
<ItemGroup>
<CompilerVisibleProperty Include="MvvmToolkitEnableINotifyPropertyChangingSupport" />
</ItemGroup>

<!--
Configuration for the feature switches (to support IL trimming).
See the 'ILLink.Substitutions.xml' file for more details on that.
Expand Down