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 @@ -17,3 +17,4 @@ MVVMTK0009 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
MVVMTK0010 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0011 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0012 | Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0013 | Microsoft.CodeAnalysis.CSharp.CSharpParseOptions | Error | See https://aka.ms/mvvmtoolkit/error
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ public void Execute(GeneratorExecutionContext context)
return;
}

// Validate the language version. Note that we're emitting this diagnostic in each generator (excluding the one
// only emitting the nullability annotation attributes if missing) so that the diagnostic is emitted only when
// users are using one of these generators, and not by default as soon as they add a reference to the MVVM Toolkit.
// This ensures that users not using any of the source generators won't be broken when upgrading to this new version.
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 })
{
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
}

// Sets of discovered property names
HashSet<string>
propertyChangedNames = new(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.CodeAnalysis.Text;
using Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;

namespace Microsoft.Toolkit.Mvvm.SourceGenerators
{
Expand All @@ -39,6 +40,12 @@ public void Execute(GeneratorExecutionContext context)
return;
}

// Validate the language version
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 })
{
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
}

// Get the symbol for the ValidationAttribute type
INamedTypeSymbol validationSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute")!;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Microsoft.CodeAnalysis.SymbolDisplayTypeQualificationStyle;
using static Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;

namespace Microsoft.Toolkit.Mvvm.SourceGenerators
{
Expand Down Expand Up @@ -67,6 +68,12 @@ public void Execute(GeneratorExecutionContext context)
return;
}

// Validate the language version
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 })
{
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
}

// Load the syntax tree with the members to generate
SyntaxTree sourceSyntaxTree = LoadSourceSyntaxTree();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.ComponentModel;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics
{
Expand Down Expand Up @@ -201,7 +202,23 @@ internal static class DiagnosticDescriptors
category: typeof(ICommandGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: $"Cannot apply [ICommand] to methods with a signature that doesn't match any of the existing relay command types.",
description: "Cannot apply [ICommand] to methods with a signature that doesn't match any of the existing relay command types.",
helpLinkUri: "https://aka.ms/mvvmtoolkit");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when an unsupported C# language version is being used.
/// <para>
/// Format: <c>"The method {0}.{1} cannot be used to generate a command property, as its signature isn't compatible with any of the existing relay command types"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor UnsupportedCSharpLanguageVersionError = new(
id: "MVVMTK0013",
title: "Unsupported C# language version",
messageFormat: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 9.0",
category: typeof(CSharpParseOptions).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 9.0. Make sure to add <LangVersion>9.0</LangVersion> (or above) to your .csproj file.",
helpLinkUri: "https://aka.ms/mvvmtoolkit");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public void Execute(GeneratorExecutionContext context)
return;
}

// Validate the language version
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 })
{
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
}

foreach (var items in syntaxReceiver.GatheredInfo.GroupBy<SyntaxReceiver.Item, INamedTypeSymbol>(static item => item.MethodSymbol.ContainingType, SymbolEqualityComparer.Default))
{
if (items.Key.DeclaringSyntaxReferences.Length > 0 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis.Text;
using Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;

namespace Microsoft.Toolkit.Mvvm.SourceGenerators
{
Expand All @@ -38,6 +39,12 @@ public void Execute(GeneratorExecutionContext context)
return;
}

// Validate the language version
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 })
{
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
}

// Get the symbol for the IRecipient<T> interface type
INamedTypeSymbol iRecipientSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.Messaging.IRecipient`1")!;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,31 +241,177 @@ public partial class SampleViewModel
VerifyGeneratedDiagnostics<ICommandGenerator>(source, "MVVMTK0012");
}

[TestCategory("Mvvm")]
[TestMethod]
public void UnsupportedCSharpLanguageVersion_FromINotifyPropertyChangedGenerator()
{
string source = @"
using Microsoft.Toolkit.Mvvm.ComponentModel;

namespace MyApp
{
[INotifyPropertyChanged]
public partial class SampleViewModel
{
}
}";

VerifyGeneratedDiagnostics<INotifyPropertyChangedGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
}

[TestCategory("Mvvm")]
[TestMethod]
public void UnsupportedCSharpLanguageVersion_FromObservableObjectGenerator()
{
string source = @"
using Microsoft.Toolkit.Mvvm.ComponentModel;

namespace MyApp
{
[ObservableObject]
public partial class SampleViewModel
{
}
}";

VerifyGeneratedDiagnostics<ObservableObjectGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
}

[TestCategory("Mvvm")]
[TestMethod]
public void UnsupportedCSharpLanguageVersion_FromObservablePropertyGenerator()
{
string source = @"
using Microsoft.Toolkit.Mvvm.ComponentModel;

namespace MyApp
{
[INotifyPropertyChanged]
public partial class SampleViewModel
{
[ObservableProperty]
private string name;
}
}";

VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
}

[TestCategory("Mvvm")]
[TestMethod]
public void UnsupportedCSharpLanguageVersion_FromObservableValidatorValidateAllPropertiesGenerator()
{
string source = @"
using Microsoft.Toolkit.Mvvm.ComponentModel;

namespace MyApp
{
public partial class SampleViewModel : ObservableValidator
{
[Required]
public string Name { get; set; }
}
}";

VerifyGeneratedDiagnostics<ObservableValidatorValidateAllPropertiesGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
}

[TestCategory("Mvvm")]
[TestMethod]
public void UnsupportedCSharpLanguageVersion_FromICommandGenerator()
{
string source = @"
using Microsoft.Toolkit.Mvvm.Input;

namespace MyApp
{
public partial class SampleViewModel
{
[ICommand]
private void GreetUser(object value)
{
}
}
}";

VerifyGeneratedDiagnostics<ICommandGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
}

[TestCategory("Mvvm")]
[TestMethod]
public void UnsupportedCSharpLanguageVersion_FromIMessengerRegisterAllGenerator()
{
string source = @"
using Microsoft.Toolkit.Mvvm.Messaging;

namespace MyApp
{
public class MyMessage
{
}

public partial class SampleViewModel : IRecipient<MyMessage>
{
public void Receive(MyMessage message)
{
}
}
}";

VerifyGeneratedDiagnostics<IMessengerRegisterAllGenerator>(
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)),
"MVVMTK0013");
}

/// <summary>
/// Verifies the output of a source generator.
/// </summary>
/// <typeparam name="TGenerator">The generator type to use.</typeparam>
/// <param name="source">The input source to process.</param>
/// <param name="diagnosticsIds">The diagnostic ids to expect for the input source code.</param>
private void VerifyGeneratedDiagnostics<TGenerator>(string source, params string[] diagnosticsIds)
private static void VerifyGeneratedDiagnostics<TGenerator>(string source, params string[] diagnosticsIds)
where TGenerator : class, ISourceGenerator, new()
{
VerifyGeneratedDiagnostics<TGenerator>(CSharpSyntaxTree.ParseText(source), diagnosticsIds);
}

/// <summary>
/// Verifies the output of a source generator.
/// </summary>
/// <typeparam name="TGenerator">The generator type to use.</typeparam>
/// <param name="syntaxTree">The input source tree to process.</param>
/// <param name="diagnosticsIds">The diagnostic ids to expect for the input source code.</param>
private static void VerifyGeneratedDiagnostics<TGenerator>(SyntaxTree syntaxTree, params string[] diagnosticsIds)
where TGenerator : class, ISourceGenerator, new()
{
Type observableObjectType = typeof(ObservableObject);
Type validationAttributeType = typeof(ValidationAttribute);

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source);

IEnumerable<MetadataReference> references =
from assembly in AppDomain.CurrentDomain.GetAssemblies()
where !assembly.IsDynamic
let reference = MetadataReference.CreateFromFile(assembly.Location)
select reference;

CSharpCompilation compilation = CSharpCompilation.Create("original", new SyntaxTree[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
CSharpCompilation compilation = CSharpCompilation.Create(
"original",
new SyntaxTree[] { syntaxTree },
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

ISourceGenerator generator = new TGenerator();

CSharpGeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
CSharpGeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { generator }, parseOptions: (CSharpParseOptions)syntaxTree.Options);

driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics);

Expand Down