diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems index 91866191c..5e950360e 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.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs index e97cfed29..a1f4e942f 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Linq; -using System.Text; using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using CommunityToolkit.Mvvm.SourceGenerators.Helpers; @@ -75,7 +74,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Insert all members into the same partial type declaration CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(memberDeclarations); - context.AddSource($"{item.Hierarchy.FilenameHint}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource($"{item.Hierarchy.FilenameHint}.g.cs", compilationUnit); }); // Gather all property changing names @@ -92,7 +91,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (compilationUnit is not null) { - context.AddSource("__KnownINotifyPropertyChangingArgs.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource("__KnownINotifyPropertyChangingArgs.g.cs", compilationUnit); } }); @@ -110,7 +109,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (compilationUnit is not null) { - context.AddSource("__KnownINotifyPropertyChangedArgs.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource("__KnownINotifyPropertyChangedArgs.g.cs", compilationUnit); } }); } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs index 66ddec019..b4a5b1537 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Linq; -using System.Text; using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; @@ -79,7 +78,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item); - context.AddSource("__ObservableValidatorExtensions.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource("__ObservableValidatorExtensions.g.cs", compilationUnit); }); // Generate the class with all validation methods @@ -87,7 +86,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item); - context.AddSource($"{item.FilenameHint}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource($"{item.FilenameHint}.g.cs", compilationUnit); }); } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs index fc430a04f..bd1d37498 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using System.Linq; -using System.Text; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using CommunityToolkit.Mvvm.SourceGenerators.Models; using Microsoft.CodeAnalysis; @@ -34,12 +33,12 @@ public abstract partial class TransitiveMembersGenerator : IIncrementalGe /// /// The sequence of member declarations for sealed types. /// - private ImmutableArray sealedMemberDeclarations; + private readonly ImmutableArray sealedMemberDeclarations; /// /// The resulting sequence of member declarations for non sealed types. /// - private ImmutableArray nonSealedMemberDeclarations; + private readonly ImmutableArray nonSealedMemberDeclarations; /// /// Initializes a new instance of the class. @@ -108,7 +107,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ImmutableArray updatedMemberDeclarations = Execute.AdjustMemberDeclarationNullabilityAnnotations(filteredMemberDeclarations, item.MetadataInfo.IsNullabilitySupported); CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(updatedMemberDeclarations, this.classDeclaration.BaseList); - context.AddSource($"{item.Hierarchy.FilenameHint}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource($"{item.Hierarchy.FilenameHint}.g.cs", compilationUnit); }); } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/SourceProductionContextExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/SourceProductionContextExtensions.cs new file mode 100644 index 000000000..f3ba65e77 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/SourceProductionContextExtensions.cs @@ -0,0 +1,33 @@ +// 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.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class SourceProductionContextExtensions +{ + /// + /// Adds a new source file to a target instance. + /// + /// The input instance to use. + /// The name of the source file to add. + /// The instance representing the syntax tree to add. + public static void AddSource(this SourceProductionContext context, string name, CompilationUnitSyntax compilationUnit) + { +#if !ROSLYN_4_3_1_OR_GREATER + // We're fine with the extra allocation in the few cases where adjusting the filename is necessary. + // This will only ever be done when code generation is executed again anyway, which is a slow path. + name = name.Replace('+', '.').Replace('`', '_'); +#endif + + // Add the UTF8 text for the input compilation unit + context.AddSource(name, compilationUnit.GetText(Encoding.UTF8)); + } +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs index 78bdb2294..46b64a56c 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Linq; -using System.Text; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using CommunityToolkit.Mvvm.SourceGenerators.Input.Models; using CommunityToolkit.Mvvm.SourceGenerators.Models; @@ -61,7 +60,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ImmutableArray memberDeclarations = Execute.GetSyntax(item.Info.Value); CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(memberDeclarations); - context.AddSource($"{item.Hierarchy.FilenameHint}.{item.Info.Value.MethodName}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource($"{item.Hierarchy.FilenameHint}.{item.Info.Value.MethodName}.g.cs", compilationUnit); }); } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs index 03d8319e4..a9ee5b060 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Linq; -using System.Text; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using CommunityToolkit.Mvvm.SourceGenerators.Messaging.Models; using Microsoft.CodeAnalysis; @@ -84,7 +83,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item); - context.AddSource("__IMessengerExtensions.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource("__IMessengerExtensions.g.cs", compilationUnit); }); // Generate the class with all registration methods @@ -92,7 +91,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item); - context.AddSource($"{item.FilenameHint}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + context.AddSource($"{item.FilenameHint}.g.cs", compilationUnit); }); } } diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj index 947810b2e..849698ae5 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj index f8974ac8f..0ba357902 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj @@ -2,6 +2,7 @@ net472;net6.0;net7.0 + $(DefineConstants);ROSLYN_4_3_1_OR_GREATER diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs index 0a357ffac..88567b938 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs @@ -193,6 +193,92 @@ public object? A VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } + [TestMethod] + public void ObservablePropertyWithinGenericAndNestedTypes() + { + string source = """ + using System.ComponentModel; + using CommunityToolkit.Mvvm.ComponentModel; + + #nullable enable + + namespace MyApp; + + partial class Foo + { + partial class MyViewModel : ObservableObject + { + [ObservableProperty] + private string? a; + } + } + """; + + string result = """ + // + #pragma warning disable + #nullable enable + namespace MyApp + { + partial class Foo + { + partial class MyViewModel + { + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public string? A + { + get => a; + set + { + if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(a, value)) + { + OnAChanging(value); + OnAChanging(default, value); + OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A); + a = value; + OnAChanged(value); + OnAChanged(default, value); + OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A); + } + } + } + + /// Executes the logic for when is changing. + /// The new property value being set. + /// This method is invoked right before the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")] + partial void OnAChanging(string? value); + /// Executes the logic for when is changing. + /// The previous property value that is being replaced. + /// The new property value being set. + /// This method is invoked right before the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")] + partial void OnAChanging(string? oldValue, string? newValue); + /// Executes the logic for when just changed. + /// The new property value that was set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")] + partial void OnAChanged(string? value); + /// Executes the logic for when just changed. + /// The previous property value that was replaced. + /// The new property value that was set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")] + partial void OnAChanged(string? oldValue, string? newValue); + } + } + } + """; + +#if ROSLYN_4_3_1_OR_GREATER + VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.Foo+MyViewModel`1.g.cs", result)); +#else + VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.Foo.MyViewModel_1.g.cs", result)); +#endif + } + /// /// Generates the requested sources /// @@ -212,7 +298,7 @@ from assembly in AppDomain.CurrentDomain.GetAssemblies() let reference = MetadataReference.CreateFromFile(assembly.Location) select reference; - SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp11)); + SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10)); // Create a syntax tree with the input source CSharpCompilation compilation = CSharpCompilation.Create(