diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitCameraInitializationAnalyzerTests.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitCameraInitializationAnalyzerTests.cs index 59cb0c45fb..cc25e18fde 100644 --- a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitCameraInitializationAnalyzerTests.cs +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitCameraInitializationAnalyzerTests.cs @@ -118,6 +118,43 @@ public static MauiApp CreateMauiApp() await VerifyCameraToolkitAnalyzer(source, Diagnostic().WithSpan(12, 4, 12, 61).WithSeverity(DiagnosticSeverity.Error)); } + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitCameraWrapInPreprocessorDirectives() + { + const string source = + /* language=C#-test */ + //lang=csharp + """ + 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() + #if ANDROID || IOS + .UseMauiCommunityToolkitCamera() + #endif + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }); + + return builder.Build(); + } + } + } + """; + + await VerifyCameraToolkitAnalyzer(source); + } + static Task VerifyCameraToolkitAnalyzer(string source, params IReadOnlyList diagnosticResults) { return VerifyAnalyzerAsync( diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs index 389c8d68dd..b03af7dcd4 100644 --- a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitInitializationAnalyzerTests.cs @@ -118,6 +118,79 @@ public static MauiApp CreateMauiApp() await VerifyMauiToolkitAnalyzer(source, Diagnostic().WithSpan(12, 4, 12, 61).WithSeverity(DiagnosticSeverity.Error)); } + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitWrapInPreprocessorDirectives() + { + const string source = + /* language=C#-test */ + //lang=csharp + """ + 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() + #if ANDROID || IOS + .UseMauiCommunityToolkit() + #endif + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }); + + return builder.Build(); + } + } + } + """; + + + await VerifyMauiToolkitAnalyzer(source); + } + + [Fact] + public async Task VerifyErrorsWhenUseMauiCommunityToolkitIsCommentedOut() + { + const string source = + /* language=C#-test */ + //lang=csharp + """ + 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, Diagnostic().WithSpan(12, 4, 12, 61).WithSeverity(DiagnosticSeverity.Error)); + } + static Task VerifyMauiToolkitAnalyzer(string source, params IReadOnlyList expected) { return VerifyAnalyzerAsync( diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs index 2cff57755f..da013837c2 100644 --- a/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs +++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/UseCommunityToolkitMediaElementInitializationAnalyzerTests.cs @@ -1,4 +1,5 @@ -using CommunityToolkit.Maui.MediaElement.Analyzers; +using CommunityToolkit.Maui.Core; +using CommunityToolkit.Maui.MediaElement.Analyzers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Testing; using Xunit; @@ -118,12 +119,49 @@ public static MauiApp CreateMauiApp() await VerifyMediaElementToolkitAnalyzer(source, Diagnostic().WithSpan(12, 4, 12, 61).WithSeverity(DiagnosticSeverity.Error)); } + [Fact] + public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitMediaElementWrapInPreprocessorDirectives() + { + const string source = + /* language=C#-test */ + //lang=csharp + """ + 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() + #if ANDROID || IOS + .UseMauiCommunityToolkitMediaElement() + #endif + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }); + + return builder.Build(); + } + } + } + """; + + await VerifyMediaElementToolkitAnalyzer(source); + } + static Task VerifyMediaElementToolkitAnalyzer(string source, params IReadOnlyList diagnosticResults) { return VerifyAnalyzerAsync( source, [ - typeof(Views.MediaElement) // CommunityToolkit.Maui.MediaElement + typeof(MediaElementOptions) // CommunityToolkit.Maui.MediaElement ], diagnosticResults); } diff --git a/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs b/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs index 334bdc3dd1..52dc929628 100644 --- a/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs +++ b/src/CommunityToolkit.Maui.Analyzers/UseCommunityToolkitInitializationAnalyzer.cs @@ -10,7 +10,6 @@ namespace CommunityToolkit.Maui.Analyzers; public class UseCommunityToolkitInitializationAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "MCT001"; - const string category = "Initialization"; const string useMauiAppMethodName = "UseMauiApp"; const string useMauiCommunityToolkitMethodName = "UseMauiCommunityToolkit"; @@ -18,7 +17,6 @@ public class UseCommunityToolkitInitializationAnalyzer : DiagnosticAnalyzer 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)); - static readonly DiagnosticDescriptor rule = new(DiagnosticId, title, messageFormat, category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: description); public override ImmutableArray SupportedDiagnostics { get; } = [rule]; @@ -32,21 +30,42 @@ public override void Initialize(AnalysisContext context) static void AnalyzeNode(SyntaxNodeAnalysisContext context) { - if (context.Node is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: useMauiAppMethodName } } invocationExpression) + if (context.Node is InvocationExpressionSyntax invocationExpression + && invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression + && memberAccessExpression.Name.Identifier.ValueText == useMauiAppMethodName) { - var root = invocationExpression.SyntaxTree.GetRoot(); - var methodDeclaration = root.FindNode(invocationExpression.FullSpan) + var methodDeclaration = invocationExpression .Ancestors() .OfType() .FirstOrDefault(); - if (methodDeclaration is not null - && !methodDeclaration.DescendantNodes().OfType().Any(static n => - n.Expression is MemberAccessExpressionSyntax { Name.Identifier.ValueText: useMauiCommunityToolkitMethodName })) + if (methodDeclaration is not null && !HasUseMauiCommunityToolkitCall(methodDeclaration)) { var diagnostic = Diagnostic.Create(rule, invocationExpression.GetLocation()); context.ReportDiagnostic(diagnostic); } } } + + static bool HasUseMauiCommunityToolkitCall(MethodDeclarationSyntax methodDeclaration) + { + // Check syntax nodes first (handles active code) + var hasInSyntaxTree = methodDeclaration + .DescendantNodes() + .OfType() + .Any(static invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess + && memberAccess.Name.Identifier.ValueText == useMauiCommunityToolkitMethodName); + + if (hasInSyntaxTree) + { + return true; + } + + // Check trivia (comments, preprocessor directives, disabled code) + return methodDeclaration + .DescendantTrivia() + .Any(static trivia => + trivia.IsKind(SyntaxKind.DisabledTextTrivia) && + trivia.ToString().Contains(useMauiCommunityToolkitMethodName, StringComparison.Ordinal)); + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs b/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs index f0ad86b325..ff01cf365b 100644 --- a/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs +++ b/src/CommunityToolkit.Maui.Camera.Analyzers/UseCommunityToolkitCameraInitializationAnalyzer.cs @@ -36,20 +36,37 @@ static void AnalyzeNode(SyntaxNodeAnalysisContext context) && invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name.Identifier.ValueText == useMauiAppMethodName) { - var root = invocationExpression.SyntaxTree.GetRoot(); - var methodDeclaration = root.FindNode(invocationExpression.FullSpan) + var methodDeclaration = invocationExpression .Ancestors() .OfType() .FirstOrDefault(); - if (methodDeclaration is not null - && !methodDeclaration.DescendantNodes().OfType().Any(static n => - n.Expression is MemberAccessExpressionSyntax m && - m.Name.Identifier.ValueText == useMauiCommunityToolkitCameraMethodName)) + if (methodDeclaration is not null && !HasUseMauiCommunityToolkitCameraCall(methodDeclaration)) { var diagnostic = Diagnostic.Create(rule, invocationExpression.GetLocation()); context.ReportDiagnostic(diagnostic); } } } + + static bool HasUseMauiCommunityToolkitCameraCall(MethodDeclarationSyntax methodDeclaration) + { + // Check syntax nodes first (handles active code) + var hasInSyntaxTree = methodDeclaration + .DescendantNodes() + .OfType() + .Any(static invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess + && memberAccess.Name.Identifier.ValueText == useMauiCommunityToolkitCameraMethodName); + + if (hasInSyntaxTree) + { + return true; + } + + // Check trivia (comments, preprocessor directives, disabled code) + return methodDeclaration + .DescendantTrivia() + .Any(static trivia => trivia.IsKind(SyntaxKind.DisabledTextTrivia) + && trivia.ToString().Contains(useMauiCommunityToolkitCameraMethodName, StringComparison.Ordinal)); + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs b/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs index 3cea047de9..8d3a379351 100644 --- a/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs +++ b/src/CommunityToolkit.Maui.MediaElement.Analyzers/UseCommunityToolkitMediaElementInitializationAnalyzer.cs @@ -36,20 +36,37 @@ static void AnalyzeNode(SyntaxNodeAnalysisContext context) && invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name.Identifier.ValueText == useMauiAppMethodName) { - var root = invocationExpression.SyntaxTree.GetRoot(); - var methodDeclaration = root.FindNode(invocationExpression.FullSpan) + var methodDeclaration = invocationExpression .Ancestors() .OfType() .FirstOrDefault(); - if (methodDeclaration is not null - && !methodDeclaration.DescendantNodes().OfType().Any(static n => - n.Expression is MemberAccessExpressionSyntax m && - m.Name.Identifier.ValueText == useMauiCommunityToolkitMediaElementMethodName)) + if (methodDeclaration is not null && !HasUseMauiCommunityToolkitMediaElementCall(methodDeclaration)) { var diagnostic = Diagnostic.Create(rule, invocationExpression.GetLocation()); context.ReportDiagnostic(diagnostic); } } } + + static bool HasUseMauiCommunityToolkitMediaElementCall(MethodDeclarationSyntax methodDeclaration) + { + // Check syntax nodes first (handles active code) + var hasInSyntaxTree = methodDeclaration + .DescendantNodes() + .OfType() + .Any(static invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess + && memberAccess.Name.Identifier.ValueText == useMauiCommunityToolkitMediaElementMethodName); + + if (hasInSyntaxTree) + { + return true; + } + + // Check trivia (comments, preprocessor directives, disabled code) + return methodDeclaration + .DescendantTrivia() + .Any(static trivia => trivia.IsKind(SyntaxKind.DisabledTextTrivia) + && trivia.ToString().Contains(useMauiCommunityToolkitMediaElementMethodName, StringComparison.Ordinal)); + } } \ No newline at end of file