diff --git a/.editorconfig b/.editorconfig index 46aa72db0..467b91c05 100644 --- a/.editorconfig +++ b/.editorconfig @@ -81,6 +81,7 @@ csharp_style_conditional_delegate_call = true:suggestion # Collection Expressions dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_diagnostic.IDE0303.severity = suggestion # Naming rules diff --git a/samples/CommunityToolkit.Maui.Sample/MauiProgram.cs b/samples/CommunityToolkit.Maui.Sample/MauiProgram.cs index 1a875d7d4..c7625ee34 100644 --- a/samples/CommunityToolkit.Maui.Sample/MauiProgram.cs +++ b/samples/CommunityToolkit.Maui.Sample/MauiProgram.cs @@ -31,15 +31,14 @@ using Microsoft.Extensions.Http.Resilience; using Microsoft.Extensions.Logging; using Microsoft.Maui.LifecycleEvents; -using Microsoft.Maui.Platform; using Polly; - #if WINDOWS10_0_17763_0_OR_GREATER using Microsoft.UI; using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml.Media; +using Microsoft.Maui.Platform; #endif [assembly: XamlCompilation(XamlCompilationOptions.Compile)] @@ -52,12 +51,12 @@ public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder() #if DEBUG - .UseMauiCommunityToolkit(options => + .UseMauiCommunityToolkit(static options => { options.SetShouldEnableSnackbarOnWindows(true); }) #else - .UseMauiCommunityToolkit(options => + .UseMauiCommunityToolkit(static options => { options.SetShouldEnableSnackbarOnWindows(true); options.SetShouldSuppressExceptionsInConverters(true); @@ -70,7 +69,7 @@ public static MauiApp CreateMauiApp() .UseMauiCommunityToolkitMediaElement() .UseMauiCommunityToolkitMaps("KEY") // You should add your own key here from bingmapsportal.com .UseMauiApp() - .ConfigureFonts(fonts => + .ConfigureFonts(static fonts => { fonts.AddFont("Font Awesome 6 Brands-Regular-400.otf", FontFamilies.FontAwesomeBrands); }); @@ -79,13 +78,13 @@ public static MauiApp CreateMauiApp() builder.ConfigureLifecycleEvents(events => { #if WINDOWS10_0_17763_0_OR_GREATER - events.AddWindows(wndLifeCycleBuilder => + events.AddWindows(static windowLifeCycleBuilder => { - wndLifeCycleBuilder.OnWindowCreated(window => + windowLifeCycleBuilder.OnWindowCreated(window => { window.SystemBackdrop = new MicaBackdrop { Kind = MicaKind.Base }; - var titleBar = window.GetAppWindow()!.TitleBar; + var titleBar = window.GetAppWindow()?.TitleBar ?? throw new InvalidOperationException("App Window Cannot be Null"); titleBar.PreferredHeightOption = TitleBarHeightOption.Tall; @@ -105,7 +104,7 @@ public static MauiApp CreateMauiApp() }); builder.Services.AddHttpClient() - .AddStandardResilienceHandler(options => options.Retry = new MobileHttpRetryStrategyOptions()); + .AddStandardResilienceHandler(static options => options.Retry = new MobileHttpRetryStrategyOptions()); builder.Services.AddSingleton(); diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj b/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj index 7077cb689..dc16f172a 100644 --- a/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj @@ -1,4 +1,4 @@ - + $(NetVersion) @@ -18,7 +18,7 @@ - + @@ -30,7 +30,11 @@ + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitCameraInitializationAnalyzerTests.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitCameraInitializationAnalyzerTests.cs new file mode 100644 index 000000000..be7f913f2 --- /dev/null +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitCameraInitializationAnalyzerTests.cs @@ -0,0 +1,117 @@ +using CommunityToolkit.Maui.Camera.Analyzers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using static CommunityToolkit.Maui.Analyzers.UnitTests.CSharpCodeFixVerifier; + +namespace CommunityToolkit.Maui.Analyzers.UnitTests; +public class UseCommunityToolkitCameraInitializationAnalyzerTests +{ + [Fact] + public void UseCommunityToolkitMediaElementInitializationAnalyzerId() + { + Assert.Equal("MCTC001", UseCommunityToolkitCameraInitializationAnalyzer.DiagnosticId); + } + + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitCamera() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder.UseMauiApp() + .UseMauiCommunityToolkitCamera() + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build(); + } + } +}"; + + await VerifyCameraToolkitAnalyzer(source); + } + + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitCameraHasAdditonalWhitespace() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder (); + builder.UseMauiApp () + .UseMauiCommunityToolkitCamera () + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build (); + } + } +}"; + + await VerifyCameraToolkitAnalyzer(source); + } + + [Fact] + public async Task VerifyErrorsWhenMissingUseMauiCommunityToolkitCamera() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder.UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build(); + } + } +}"; + + await VerifyCameraToolkitAnalyzer(source, Diagnostic().WithSpan(13, 4, 13, 61).WithSeverity(DiagnosticSeverity.Error)); + } + + static Task VerifyCameraToolkitAnalyzer(string source, params DiagnosticResult[] diagnosticResults) + { + return VerifyAnalyzerAsync( + source, + [ + typeof(Views.CameraView) // CommunityToolkit.Maui.Camera + ], + diagnosticResults); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs index c8c5e80f5..4fc02d3fe 100644 --- a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs @@ -1,4 +1,7 @@ -using Xunit; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using static CommunityToolkit.Maui.Analyzers.UnitTests.CSharpAnalyzerVerifier; namespace CommunityToolkit.Maui.Analyzers.UnitTests; @@ -9,4 +12,107 @@ public void UseCommunityToolkitInitializationAnalyzerId() { Assert.Equal("MCT001", UseCommunityToolkitInitializationAnalyzer.DiagnosticId); } + + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkit() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder.UseMauiApp() + .UseMauiCommunityToolkit() + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build(); + } + } +}"; + + await VerifyMauiToolkitAnalyzer(source); + } + + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitHasAdditonalWhitespace() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder (); + builder.UseMauiApp () + .UseMauiCommunityToolkit () + .ConfigureFonts (fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build (); + } + } +}"; + + await VerifyMauiToolkitAnalyzer(source); + } + + [Fact] + public async Task VerifyErrorsWhenMissingUseMauiCommunityToolkit() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder.UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build(); + } + } +}"; + + await VerifyMauiToolkitAnalyzer(source, Diagnostic().WithSpan(13, 4, 13, 61).WithSeverity(DiagnosticSeverity.Error)); + } + + static Task VerifyMauiToolkitAnalyzer(string source, params DiagnosticResult[] expected) + { + return VerifyAnalyzerAsync( + source, + [ + typeof(Options), // CommunityToolkit.Maui + typeof(Core.Options), // CommunityToolkit.Maui.Core; + ], + expected); + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs index f1ede318b..20fd14dac 100644 --- a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs @@ -1,5 +1,8 @@ using CommunityToolkit.Maui.MediaElement.Analyzers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Xunit; +using static CommunityToolkit.Maui.Analyzers.UnitTests.CSharpCodeFixVerifier; namespace CommunityToolkit.Maui.Analyzers.UnitTests; @@ -10,4 +13,106 @@ public void UseCommunityToolkitMediaElementInitializationAnalyzerId() { Assert.Equal("MCTME001", UseCommunityToolkitMediaElementInitializationAnalyzer.DiagnosticId); } + + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitMediaElement() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder.UseMauiApp() + .UseMauiCommunityToolkitMediaElement() + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build(); + } + } +}"; + + await VerifyMediaElementToolkitAnalyzer(source); + } + + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitMediaElementHasAdditonalWhitespace() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder (); + builder.UseMauiApp () + .UseMauiCommunityToolkitMediaElement () + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build (); + } + } +}"; + + await VerifyMediaElementToolkitAnalyzer(source); + } + + [Fact] + public async Task VerifyErrorsWhenMissingUseMauiCommunityToolkitMediaElement() + { + const string source = @" +namespace CommunityToolkit.Maui.Analyzers.UnitTests +{ + using Microsoft.Maui.Controls.Hosting; + using Microsoft.Maui.Hosting; + using CommunityToolkit.Maui; + + public static class MauiProgram + { + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder.UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont(""OpenSans-Regular.ttf"", ""OpenSansRegular""); + fonts.AddFont(""OpenSans-Semibold.ttf"", ""OpenSansSemibold""); + }); + + return builder.Build(); + } + } +}"; + + await VerifyMediaElementToolkitAnalyzer(source, Diagnostic().WithSpan(13, 4, 13, 61).WithSeverity(DiagnosticSeverity.Error)); + } + + static Task VerifyMediaElementToolkitAnalyzer(string source, params DiagnosticResult[] diagnosticResults) + { + return VerifyAnalyzerAsync( + source, + [ + typeof(Views.MediaElement) // CommunityToolkit.Maui.MediaElement + ], + diagnosticResults); + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs new file mode 100644 index 000000000..de07fb643 --- /dev/null +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs @@ -0,0 +1,49 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CommunityToolkit.Maui.Analyzers.UnitTests; +public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() +{ + public class Test : CSharpAnalyzerTest + { + public Test(params Type[] assembliesUnderTest) + { +#if NET8_0 + ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net80; +#else +#error ReferenceAssemblies must be updated to current version of .NET +#endif + List typesForAssembliesUnderTest = + [ + typeof(Microsoft.Maui.Controls.Xaml.Extensions), // Microsoft.Maui.Controls.Xaml + typeof(MauiApp),// Microsoft.Maui.Hosting + typeof(Application), // Microsoft.Maui.Controls + ]; + typesForAssembliesUnderTest.AddRange(assembliesUnderTest); + + foreach (Type type in typesForAssembliesUnderTest) + { + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(type.Assembly.Location)); + } + + SolutionTransforms.Add((solution, projectId) => + { + ArgumentNullException.ThrowIfNull(solution); + + if (solution.GetProject(projectId) is not Project project) + { + throw new ArgumentException("Invalid ProjectId"); + } + + var compilationOptions = project.CompilationOptions ?? throw new InvalidOperationException($"{nameof(project.CompilationOptions)} cannot be null"); + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpAnalyzerVerifier`1.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpAnalyzerVerifier`1.cs new file mode 100644 index 000000000..420c9bc22 --- /dev/null +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpAnalyzerVerifier`1.cs @@ -0,0 +1,34 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace CommunityToolkit.Maui.Analyzers.UnitTests; + +public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() +{ + /// + public static DiagnosticResult Diagnostic() + => CSharpAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, Type[] assembliesUnderTest, params DiagnosticResult[] expected) + { + var test = new Test(assembliesUnderTest) + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs new file mode 100644 index 000000000..c44215a56 --- /dev/null +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs @@ -0,0 +1,53 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace CommunityToolkit.Maui.Analyzers.UnitTests; +public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() +{ + protected class Test : CSharpCodeFixTest + { + public Test(params Type[] assembliesUnderTest) + { +#if NET8_0 + ReferenceAssemblies = ReferenceAssemblies.Net.Net80; +#else +#error ReferenceAssemblies must be updated to current version of .NET +#endif + List typesForAssembliesUnderTest = + [ + typeof(Microsoft.Maui.Controls.Xaml.Extensions), // Microsoft.Maui.Controls.Xaml + typeof(MauiApp),// Microsoft.Maui.Hosting + typeof(Application), // Microsoft.Maui.Controls + ]; + typesForAssembliesUnderTest.AddRange(assembliesUnderTest); + + foreach (Type type in typesForAssembliesUnderTest) + { + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(type.Assembly.Location)); + } + + SolutionTransforms.Add((solution, projectId) => + { + ArgumentNullException.ThrowIfNull(solution); + + if (solution.GetProject(projectId) is not Project project) + { + throw new ArgumentException("Invalid ProjectId"); + } + + var compilationOptions = project.CompilationOptions ?? throw new InvalidOperationException($"{nameof(project.CompilationOptions)} cannot be null"); + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs new file mode 100644 index 000000000..bb99fd1c6 --- /dev/null +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -0,0 +1,56 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace CommunityToolkit.Maui.Analyzers.UnitTests; +public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() +{ + /// + public static DiagnosticResult Diagnostic() + => CSharpCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, Type[] assembliesUnderTest, params DiagnosticResult[] expected) + { + var test = new Test(assembliesUnderTest) + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, [expected], fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource, params Type[] assembliesUnderTest) + { + var test = new Test(assembliesUnderTest) + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpVerifierHelper.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpVerifierHelper.cs new file mode 100644 index 000000000..57e6c7e34 --- /dev/null +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/Verifiers/CSharpVerifierHelper.cs @@ -0,0 +1,30 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace CommunityToolkit.Maui.Analyzers.UnitTests; +static class CSharpVerifierHelper +{ + /// + /// By default, the compiler reports diagnostics for nullable reference types at + /// , and the analyzer test framework defaults to only validating + /// diagnostics at . This map contains all compiler diagnostic IDs + /// related to nullability mapped to , which is then used to enable all + /// of these warnings for default validation during analyzer and code fix tests. + /// + internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler(); + + static ImmutableDictionary GetNullableWarningsFromCompiler() + { + string[] args = ["/warnaserror:nullable"]; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + // Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings + .SetItem("CS8632", ReportDiagnostic.Error) + .SetItem("CS8669", ReportDiagnostic.Error); + + return nullableWarnings; + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Shipped.md b/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Shipped.md index 15b282168..7de2e2858 100644 --- a/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Shipped.md +++ b/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Shipped.md @@ -2,6 +2,6 @@ ### New Rules -Rule ID | Severity | Notes ---------|----------|------- -MCT001 | Error | `.UseMauiCommunityToolkit()` Not Found on MauiAppBuilder \ No newline at end of file +| Rule ID | Severity | Notes | +|---------|----------|----------------------------------------------------------| +| MCT001 | Error | `.UseMauiCommunityToolkit()` Not Found on MauiAppBuilder | \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Unshipped.md b/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Unshipped.md index fed559a3e..dcd8b2e7a 100644 --- a/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/CommunityToolkit.Maui.Analyzers/AnalyzerReleases.Unshipped.md @@ -2,5 +2,5 @@ ### New Rules -Rule ID | Severity | Notes ---------|----------|------- \ No newline at end of file +| Rule ID | Severity | Notes | +|---------|----------|-------| \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Analyzers/CommunityToolkit.Maui.Analyzers.csproj b/src/CommunityToolkit.Maui.Analyzers/CommunityToolkit.Maui.Analyzers.csproj index 2aa45b70d..f2b434ee2 100644 --- a/src/CommunityToolkit.Maui.Analyzers/CommunityToolkit.Maui.Analyzers.csproj +++ b/src/CommunityToolkit.Maui.Analyzers/CommunityToolkit.Maui.Analyzers.csproj @@ -22,4 +22,9 @@ + + + + + diff --git a/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs b/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs index fc95d3144..f58d7fe30 100644 --- a/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs +++ b/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs @@ -12,6 +12,8 @@ public class UseCommunityToolkitInitializationAnalyzer : DiagnosticAnalyzer public const string DiagnosticId = "MCT001"; const string category = "Initialization"; + const string useMauiAppMethodName = "UseMauiApp"; + const string useMauiCommunityToolkitMethodName = "UseMauiCommunityToolkit"; static readonly LocalizableString title = new LocalizableResourceString(nameof(Resources.InitializationErrorTitle), Resources.ResourceManager, typeof(Resources)); static readonly LocalizableString messageFormat = new LocalizableResourceString(nameof(Resources.InitalizationMessageFormat), Resources.ResourceManager, typeof(Resources)); @@ -25,64 +27,29 @@ public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ExpressionStatement); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); } static void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var expressionStatement = (ExpressionStatementSyntax)context.Node; - var root = expressionStatement.SyntaxTree.GetRoot(); - - if (HasUseMauiCommunityToolkit(root)) - { - return; - } - - if (CheckIfItIsUseMauiMethod(expressionStatement)) + if (context.Node is InvocationExpressionSyntax invocationExpression + && invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression + && memberAccessExpression.Name.Identifier.ValueText == useMauiAppMethodName) { - var expression = GetInvocationExpressionSyntax(expressionStatement); - var diagnostic = Diagnostic.Create(rule, expression.GetLocation()); - context.ReportDiagnostic(diagnostic); - } - } - - static bool CheckIfItIsUseMauiMethod(ExpressionStatementSyntax expressionStatement) => - expressionStatement.DescendantNodes() - .OfType() - .Any(x => x.Identifier.ValueText.Equals("UseMauiApp", StringComparison.Ordinal) - && x.TypeArgumentList.Arguments.Count is 1); - - static bool HasUseMauiCommunityToolkit(SyntaxNode root) - { - foreach (var method in root.DescendantNodes().OfType()) - { - if (method.DescendantNodes().OfType().Any(x => x.DescendantNodes().Any(x => x.ToString().Contains(".UseMauiCommunityToolkit(")))) + var root = invocationExpression.SyntaxTree.GetRoot(); + var methodDeclaration = root.FindNode(invocationExpression.FullSpan) + .Ancestors() + .OfType() + .FirstOrDefault(); + + if (methodDeclaration is not null + && !methodDeclaration.DescendantNodes().OfType().Any(static n => + n.Expression is MemberAccessExpressionSyntax m && + m.Name.Identifier.ValueText == useMauiCommunityToolkitMethodName)) { - return true; - } - } - - return false; - } - - static InvocationExpressionSyntax GetInvocationExpressionSyntax(SyntaxNode parent) - { - foreach (var child in parent.ChildNodes()) - { - if (child is InvocationExpressionSyntax expressionSyntax) - { - return expressionSyntax; - } - else - { - var expression = GetInvocationExpressionSyntax(child); - - if (expression is not null) - { - return expression; - } + var diagnostic = Diagnostic.Create(rule, invocationExpression.GetLocation()); + context.ReportDiagnostic(diagnostic); } } - throw new InvalidOperationException("Wow, this shouldn't happen, please open a bug here: https://github.com/CommunityToolkit/Maui/issues/new/choose"); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes/UseCommunityToolkitCameraViewInitializationAnalyzerCodeFixProvider.cs b/src/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes/UseCommunityToolkitCameraViewInitializationAnalyzerCodeFixProvider.cs index 5cd86b260..42f309ae4 100644 --- a/src/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes/UseCommunityToolkitCameraViewInitializationAnalyzerCodeFixProvider.cs +++ b/src/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes/UseCommunityToolkitCameraViewInitializationAnalyzerCodeFixProvider.cs @@ -1,6 +1,5 @@ using System.Collections.Immutable; using System.Composition; -using CommunityToolkit.Maui.Camera.Analyzers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers/AnalyzerReleases.Shipped.md b/src/CommunityToolkit.Maui.Camera.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 000000000..d6b7eb9fc --- /dev/null +++ b/src/CommunityToolkit.Maui.Camera.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,7 @@ +## Release 1.0.0 + +### New Rules + +| Rule ID | Severity | Notes | +|---------|----------|----------------------------------------------------------------| +| MCTC001 | Error | `.UseMauiCommunityToolkitCamera()` Not Found on MauiAppBuilder | \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers/AnalyzerReleases.Unshipped.md b/src/CommunityToolkit.Maui.Camera.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 000000000..402e16cb3 --- /dev/null +++ b/src/CommunityToolkit.Maui.Camera.Analyzers/AnalyzerReleases.Unshipped.md @@ -0,0 +1,6 @@ +## Release 1.0.0 + +### New Rules + +| Rule ID | Severity | Notes | +|---------|----------|-------| \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers/CommunityToolkit.Maui.Camera.Analyzers.csproj b/src/CommunityToolkit.Maui.Camera.Analyzers/CommunityToolkit.Maui.Camera.Analyzers.csproj index e8f2eb823..ce6e577cb 100644 --- a/src/CommunityToolkit.Maui.Camera.Analyzers/CommunityToolkit.Maui.Camera.Analyzers.csproj +++ b/src/CommunityToolkit.Maui.Camera.Analyzers/CommunityToolkit.Maui.Camera.Analyzers.csproj @@ -21,4 +21,9 @@ + + + + + diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.Designer.cs b/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.Designer.cs index 45e1bc183..efdb3105c 100644 --- a/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.Designer.cs +++ b/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // @@ -11,44 +11,32 @@ namespace CommunityToolkit.Maui.Camera.Analyzers { using System; - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// This class was generated by MSBuild using the GenerateResource task. - /// To add or remove a member, edit your .resx file then rerun MSBuild. - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Build.Tasks.StronglyTypedResourceBuilder", "15.1.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.1.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { - private static global::System.Resources.ResourceManager resourceMan; + private static System.Resources.ResourceManager resourceMan; - private static global::System.Globalization.CultureInfo resourceCulture; + private static System.Globalization.CultureInfo resourceCulture; - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CommunityToolkit.Maui.Camera.Analyzers.Resources", typeof(Resources).Assembly); + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("CommunityToolkit.Maui.Camera.Analyzers.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -57,27 +45,18 @@ internal Resources() { } } - /// - /// Looks up a localized string similar to `.UseMauiCommunityToolkitCamera()` must be chained to `.UseMauiApp<T>()`. - /// internal static string InitalizationMessageFormat { get { return ResourceManager.GetString("InitalizationMessageFormat", resourceCulture); } } - /// - /// Looks up a localized string similar to `.UseMauiCommunityToolkitCamera()` is required to initalize .NET MAUI Community Toolkit Camera. - /// internal static string InitializationErrorMessage { get { return ResourceManager.GetString("InitializationErrorMessage", resourceCulture); } } - /// - /// Looks up a localized string similar to `.UseMauiCommunityToolkitCamera()` Not Found on MauiAppBuilder. - /// internal static string InitializationErrorTitle { get { return ResourceManager.GetString("InitializationErrorTitle", resourceCulture); diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.resx b/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.resx index 8b2df30f5..26c000ff2 100644 --- a/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.resx +++ b/src/CommunityToolkit.Maui.Camera.Analyzers/Resources.resx @@ -16,7 +16,7 @@ `.UseMauiCommunityToolkitCamera()` must be chained to `.UseMauiApp<T>()` - `.UseMauiCommunityToolkitCamera()` is required to initalize .NET MAUI Community Toolkit Camera + `.UseMauiCommunityToolkitCamera()` is required to initalize .NET MAUI Community Toolkit Camera. `.UseMauiCommunityToolkitCamera()` Not Found on MauiAppBuilder diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs b/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs index c410db472..f0ad86b32 100644 --- a/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs +++ b/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Immutable; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -13,6 +12,9 @@ public class UseCommunityToolkitCameraInitializationAnalyzer : DiagnosticAnalyze public const string DiagnosticId = "MCTC001"; const string category = "Initialization"; + const string useMauiAppMethodName = "UseMauiApp"; + const string useMauiCommunityToolkitCameraMethodName = "UseMauiCommunityToolkitCamera"; + static readonly LocalizableString title = new LocalizableResourceString(nameof(Resources.InitializationErrorTitle), Resources.ResourceManager, typeof(Resources)); static readonly LocalizableString messageFormat = new LocalizableResourceString(nameof(Resources.InitalizationMessageFormat), Resources.ResourceManager, typeof(Resources)); static readonly LocalizableString description = new LocalizableResourceString(nameof(Resources.InitializationErrorMessage), Resources.ResourceManager, typeof(Resources)); @@ -25,65 +27,29 @@ public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ExpressionStatement); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); } static void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var expressionStatement = (ExpressionStatementSyntax)context.Node; - var root = expressionStatement.SyntaxTree.GetRoot(); - - if (HasUseMauiCommunityToolkit(root)) - { - return; - } - - if (CheckIfItIsUseMauiMethod(expressionStatement)) - { - var expression = GetInvocationExpressionSyntax(expressionStatement); - var diagnostic = Diagnostic.Create(rule, expression.GetLocation()); - context.ReportDiagnostic(diagnostic); - } - } - - static bool CheckIfItIsUseMauiMethod(ExpressionStatementSyntax expressionStatement) => - expressionStatement.DescendantNodes() - .OfType() - .Any(x => x.Identifier.ValueText.Equals("UseMauiApp", StringComparison.Ordinal) - && x.TypeArgumentList.Arguments.Count is 1); - - static bool HasUseMauiCommunityToolkit(SyntaxNode root) - { - foreach (var method in root.DescendantNodes().OfType()) + if (context.Node is InvocationExpressionSyntax invocationExpression + && invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression + && memberAccessExpression.Name.Identifier.ValueText == useMauiAppMethodName) { - if (method.DescendantNodes().OfType().Any(x => x.DescendantNodes().Any(x => x.ToString().Contains(".UseMauiCommunityToolkitCamera(")))) + var root = invocationExpression.SyntaxTree.GetRoot(); + var methodDeclaration = root.FindNode(invocationExpression.FullSpan) + .Ancestors() + .OfType() + .FirstOrDefault(); + + if (methodDeclaration is not null + && !methodDeclaration.DescendantNodes().OfType().Any(static n => + n.Expression is MemberAccessExpressionSyntax m && + m.Name.Identifier.ValueText == useMauiCommunityToolkitCameraMethodName)) { - return true; + var diagnostic = Diagnostic.Create(rule, invocationExpression.GetLocation()); + context.ReportDiagnostic(diagnostic); } } - - return false; - } - - static InvocationExpressionSyntax GetInvocationExpressionSyntax(SyntaxNode parent) - { - foreach (var child in parent.ChildNodes()) - { - if (child is InvocationExpressionSyntax expressionSyntax) - { - return expressionSyntax; - } - else - { - var expression = GetInvocationExpressionSyntax(child); - - if (expression is not null) - { - return expression; - } - } - } - - throw new InvalidOperationException("Wow, this shouldn't happen, please open a bug here: https://github.com/CommunityToolkit/Maui/issues/new/choose"); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.windows.cs b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.windows.cs index d64191c7f..c38501977 100644 --- a/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.windows.cs +++ b/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.windows.cs @@ -19,7 +19,7 @@ async Task InternalSaveAsync(string initialPath, string fileName, Stream var extension = Path.GetExtension(fileName); if (!string.IsNullOrEmpty(extension)) { - savePicker.FileTypeChoices.Add(extension, new List { extension }); + savePicker.FileTypeChoices.Add(extension, [extension]); } savePicker.FileTypeChoices.Add("All files", allFilesExtension); diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes/UseCommunityToolkitMediaElementInitializationAnalyzerCodeFixProvider.cs b/src/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes/UseCommunityToolkitMediaElementInitializationAnalyzerCodeFixProvider.cs index 1100c2352..dff2e9b6b 100644 --- a/src/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes/UseCommunityToolkitMediaElementInitializationAnalyzerCodeFixProvider.cs +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes/UseCommunityToolkitMediaElementInitializationAnalyzerCodeFixProvider.cs @@ -1,21 +1,13 @@ -using System; -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Composition; -using System.Linq.Expressions; -using System.Reflection.Metadata.Ecma335; -using System.Runtime.InteropServices.ComTypes; -using System.Threading; -using System.Threading.Tasks; -using CommunityToolkit.Maui.MediaElement.Analyzers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -namespace CommunityToolkit.Maui.Analyzers; +namespace CommunityToolkit.Maui.MediaElement.Analyzers; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseCommunityToolkitMediaElementInitializationAnalyzerCodeFixProvider)), Shared] public class UseCommunityToolkitMediaElementInitializationAnalyzerCodeFixProvider : CodeFixProvider diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers/AnalyzerReleases.Shipped.md b/src/CommunityToolkit.Maui.MediaElement.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 000000000..e37d80b47 --- /dev/null +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,7 @@ +## Release 1.0.0 + +### New Rules + +| Rule ID | Severity | Notes | +|----------|----------|----------------------------------------------------------------------| +| MCTME001 | Error | `.UseMauiCommunityToolkitMediaElement()` Not Found on MauiAppBuilder | \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers/AnalyzerReleases.Unshipped.md b/src/CommunityToolkit.Maui.MediaElement.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 000000000..402e16cb3 --- /dev/null +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers/AnalyzerReleases.Unshipped.md @@ -0,0 +1,6 @@ +## Release 1.0.0 + +### New Rules + +| Rule ID | Severity | Notes | +|---------|----------|-------| \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers/CommunityToolkit.Maui.MediaElement.Analyzers.csproj b/src/CommunityToolkit.Maui.MediaElement.Analyzers/CommunityToolkit.Maui.MediaElement.Analyzers.csproj index cf147c3b4..bcf6df113 100644 --- a/src/CommunityToolkit.Maui.MediaElement.Analyzers/CommunityToolkit.Maui.MediaElement.Analyzers.csproj +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers/CommunityToolkit.Maui.MediaElement.Analyzers.csproj @@ -7,6 +7,9 @@ *$(MSBuildProjectFile)* + + + true @@ -18,4 +21,9 @@ + + + + + diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.Designer.cs b/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.Designer.cs index 06b2ec2e8..ef9d314a7 100644 --- a/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.Designer.cs +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // @@ -11,44 +11,32 @@ namespace CommunityToolkit.Maui.MediaElement.Analyzers { using System; - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// This class was generated by MSBuild using the GenerateResource task. - /// To add or remove a member, edit your .resx file then rerun MSBuild. - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Build.Tasks.StronglyTypedResourceBuilder", "15.1.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.1.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { - private static global::System.Resources.ResourceManager resourceMan; + private static System.Resources.ResourceManager resourceMan; - private static global::System.Globalization.CultureInfo resourceCulture; + private static System.Globalization.CultureInfo resourceCulture; - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CommunityToolkit.Maui.MediaElement.Analyzers.Resources", typeof(Resources).Assembly); + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("CommunityToolkit.Maui.MediaElement.Analyzers.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -57,27 +45,18 @@ internal Resources() { } } - /// - /// Looks up a localized string similar to `.UseMauiCommunityToolkitMediaElement()` must be chained to `.UseMauiApp<T>()`. - /// internal static string InitalizationMessageFormat { get { return ResourceManager.GetString("InitalizationMessageFormat", resourceCulture); } } - /// - /// Looks up a localized string similar to `.UseMauiCommunityToolkitMediaElement()` is required to initalize .NET MAUI Community Toolkit MediaElement. - /// internal static string InitializationErrorMessage { get { return ResourceManager.GetString("InitializationErrorMessage", resourceCulture); } } - /// - /// Looks up a localized string similar to `.UseMauiCommunityToolkitMediaElement()` Not Found on MauiAppBuilder. - /// internal static string InitializationErrorTitle { get { return ResourceManager.GetString("InitializationErrorTitle", resourceCulture); diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.resx b/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.resx index 355f6e2d9..245f8d4ec 100644 --- a/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.resx +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers/Resources.resx @@ -16,7 +16,7 @@ `.UseMauiCommunityToolkitMediaElement()` must be chained to `.UseMauiApp<T>()` - `.UseMauiCommunityToolkitMediaElement()` is required to initalize .NET MAUI Community Toolkit MediaElement + `.UseMauiCommunityToolkitMediaElement()` is required to initalize .NET MAUI Community Toolkit MediaElement. `.UseMauiCommunityToolkitMediaElement()` Not Found on MauiAppBuilder diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs b/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs index 50dd688a4..3cea047de 100644 --- a/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Immutable; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -13,6 +12,9 @@ public class UseCommunityToolkitMediaElementInitializationAnalyzer : DiagnosticA public const string DiagnosticId = "MCTME001"; const string category = "Initialization"; + const string useMauiAppMethodName = "UseMauiApp"; + const string useMauiCommunityToolkitMediaElementMethodName = "UseMauiCommunityToolkitMediaElement"; + static readonly LocalizableString title = new LocalizableResourceString(nameof(Resources.InitializationErrorTitle), Resources.ResourceManager, typeof(Resources)); static readonly LocalizableString messageFormat = new LocalizableResourceString(nameof(Resources.InitalizationMessageFormat), Resources.ResourceManager, typeof(Resources)); static readonly LocalizableString description = new LocalizableResourceString(nameof(Resources.InitializationErrorMessage), Resources.ResourceManager, typeof(Resources)); @@ -25,65 +27,29 @@ public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ExpressionStatement); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); } static void AnalyzeNode(SyntaxNodeAnalysisContext context) { - var expressionStatement = (ExpressionStatementSyntax)context.Node; - var root = expressionStatement.SyntaxTree.GetRoot(); - - if (HasUseMauiCommunityToolkit(root)) - { - return; - } - - if (CheckIfItIsUseMauiMethod(expressionStatement)) - { - var expression = GetInvocationExpressionSyntax(expressionStatement); - var diagnostic = Diagnostic.Create(rule, expression.GetLocation()); - context.ReportDiagnostic(diagnostic); - } - } - - static bool CheckIfItIsUseMauiMethod(ExpressionStatementSyntax expressionStatement) => - expressionStatement.DescendantNodes() - .OfType() - .Any(x => x.Identifier.ValueText.Equals("UseMauiApp", StringComparison.Ordinal) - && x.TypeArgumentList.Arguments.Count is 1); - - static bool HasUseMauiCommunityToolkit(SyntaxNode root) - { - foreach (var method in root.DescendantNodes().OfType()) + if (context.Node is InvocationExpressionSyntax invocationExpression + && invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression + && memberAccessExpression.Name.Identifier.ValueText == useMauiAppMethodName) { - if (method.DescendantNodes().OfType().Any(x => x.DescendantNodes().Any(x => x.ToString().Contains(".UseMauiCommunityToolkitMediaElement(")))) + var root = invocationExpression.SyntaxTree.GetRoot(); + var methodDeclaration = root.FindNode(invocationExpression.FullSpan) + .Ancestors() + .OfType() + .FirstOrDefault(); + + if (methodDeclaration is not null + && !methodDeclaration.DescendantNodes().OfType().Any(static n => + n.Expression is MemberAccessExpressionSyntax m && + m.Name.Identifier.ValueText == useMauiCommunityToolkitMediaElementMethodName)) { - return true; + var diagnostic = Diagnostic.Create(rule, invocationExpression.GetLocation()); + context.ReportDiagnostic(diagnostic); } } - - return false; - } - - static InvocationExpressionSyntax GetInvocationExpressionSyntax(SyntaxNode parent) - { - foreach (var child in parent.ChildNodes()) - { - if (child is InvocationExpressionSyntax expressionSyntax) - { - return expressionSyntax; - } - else - { - var expression = GetInvocationExpressionSyntax(child); - - if (expression is not null) - { - return expression; - } - } - } - - throw new InvalidOperationException("Wow, this shouldn't happen, please open a bug here: https://github.com/CommunityToolkit/Maui/issues/new/choose"); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.UnitTests/Converters/StringToListConverterTests.cs b/src/CommunityToolkit.Maui.UnitTests/Converters/StringToListConverterTests.cs index 6f16b7e82..c39f3a344 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Converters/StringToListConverterTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Converters/StringToListConverterTests.cs @@ -114,10 +114,10 @@ public void InvalidConverterParametersThrowArgumentException(object parameter) [Fact] public void StringToListConverterNullStringsInListTest() { -#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. - Assert.Throws(() => new StringToListConverter { Separators = new List { ",", null } }); - Assert.Throws(() => new StringToListConverter().Separators = new List { ",", null }); -#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + Assert.Throws(() => new StringToListConverter { Separators = [",", null] }); + Assert.Throws(() => new StringToListConverter().Separators = [",", null]); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } [Fact] @@ -138,8 +138,8 @@ public void StringToListConverterEmptyStringInputTest() { Assert.Throws(() => new StringToListConverter { Separator = string.Empty }); Assert.Throws(() => new StringToListConverter().Separator = string.Empty); - Assert.Throws(() => new StringToListConverter { Separators = new List { ",", string.Empty } }); - Assert.Throws(() => new StringToListConverter().Separators = new List { ",", string.Empty }); + Assert.Throws(() => new StringToListConverter { Separators = [",", string.Empty] }); + Assert.Throws(() => new StringToListConverter().Separators = [",", string.Empty]); Assert.Throws(() => new StringToListConverter().ConvertFrom(string.Empty, string.Empty)); Assert.Throws(() => new StringToListConverter().ConvertFrom(string.Empty, new[] { ",", "" })); Assert.Throws(() => ((ICommunityToolkitValueConverter)new StringToListConverter()).Convert(string.Empty, typeof(IList), string.Empty, null)); diff --git a/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs b/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs index b05e84339..5dcadbf57 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Layouts/StateContainerTests.cs @@ -10,12 +10,12 @@ namespace CommunityToolkit.Maui.UnitTests.Layouts; public class StateContainerTests : BaseTest { - readonly IList stateViews = new List - { + readonly IList stateViews = + [ new Label() { Text = "Loading" }, new Label() { Text = "Error" }, new Label() { Text = "Anything", HorizontalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.End }, - }; + ]; readonly VerticalStackLayout layout = new() { diff --git a/src/CommunityToolkit.Maui.UnitTests/Mocks/MockResourcesProvider.cs b/src/CommunityToolkit.Maui.UnitTests/Mocks/MockResourcesProvider.cs index 9d738515a..df68a731d 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Mocks/MockResourcesProvider.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Mocks/MockResourcesProvider.cs @@ -7,7 +7,7 @@ namespace CommunityToolkit.Maui.UnitTests.Mocks; class MockResourcesProvider : ISystemResourcesProvider #pragma warning restore CS0612 // Type or member is obsolete { - readonly ResourceDictionary dictionary = new(); + readonly ResourceDictionary dictionary = []; public IResourceDictionary GetSystemResources() => dictionary; } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Converters/EnumToBoolConverter.shared.cs b/src/CommunityToolkit.Maui/Converters/EnumToBoolConverter.shared.cs index a18051e0b..79a2d2d97 100644 --- a/src/CommunityToolkit.Maui/Converters/EnumToBoolConverter.shared.cs +++ b/src/CommunityToolkit.Maui/Converters/EnumToBoolConverter.shared.cs @@ -14,7 +14,7 @@ public class EnumToBoolConverter : BaseConverterOneWay /// /// Enum values, that converts to true (optional) /// - public IList TrueValues { get; } = new List(); + public IList TrueValues { get; } = []; /// /// Convert an to corresponding