Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
@@ -1,4 +1,4 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using static CommunityToolkit.Maui.Analyzers.UnitTests.CSharpCodeFixVerifier<CommunityToolkit.Maui.Analyzers.UseCommunityToolkitInitializationAnalyzer, CommunityToolkit.Maui.Analyzers.UseCommunityToolkitInitializationAnalyzerCodeFixProvider>;
Expand All @@ -7,19 +7,19 @@ namespace CommunityToolkit.Maui.Analyzers.UnitTests;

public class UseCommunityToolkitInitializationAnalyzerTests
{
[Fact]
public void UseCommunityToolkitInitializationAnalyzerId()
{
Assert.Equal("MCT001", UseCommunityToolkitInitializationAnalyzer.DiagnosticId);
}
[Fact]
public void UseCommunityToolkitInitializationAnalyzerId()
{
Assert.Equal("MCT001", UseCommunityToolkitInitializationAnalyzer.DiagnosticId);
}

[Fact]
public async Task VerifyNoErrorsWhenUseMauiCommunityToolkit()
{
const string source =
/* language=C#-test */
//lang=csharp
"""
[Fact]
public async Task VerifyNoErrorsWhenUseMauiCommunityToolkit()
{
const string source =
/* language=C#-test */
//lang=csharp
"""
namespace CommunityToolkit.Maui.Analyzers.UnitTests
{
using Microsoft.Maui.Controls.Hosting;
Expand All @@ -46,16 +46,16 @@ public static MauiApp CreateMauiApp()
""";


await VerifyMauiToolkitAnalyzer(source);
}
await VerifyMauiToolkitAnalyzer(source);
}

[Fact]
public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitHasAdditionalWhitespace()
{
const string source =
/* language=C#-test */
//lang=csharp
"""
[Fact]
public async Task VerifyNoErrorsWhenUseMauiCommunityToolkitHasAdditionalWhitespace()
{
const string source =
/* language=C#-test */
//lang=csharp
"""
namespace CommunityToolkit.Maui.Analyzers.UnitTests
{
using Microsoft.Maui.Controls.Hosting;
Expand All @@ -81,16 +81,16 @@ public static MauiApp CreateMauiApp()
}
""";

await VerifyMauiToolkitAnalyzer(source);
}
await VerifyMauiToolkitAnalyzer(source);
}

[Fact]
public async Task VerifyErrorsWhenMissingUseMauiCommunityToolkit()
{
const string source =
/* language=C#-test */
//lang=csharp
"""
[Fact]
public async Task VerifyErrorsWhenMissingUseMauiCommunityToolkit()
{
const string source =
/* language=C#-test */
//lang=csharp
"""
namespace CommunityToolkit.Maui.Analyzers.UnitTests
{
using Microsoft.Maui.Controls.Hosting;
Expand All @@ -115,17 +115,55 @@ public static MauiApp CreateMauiApp()
}
""";

await VerifyMauiToolkitAnalyzer(source, Diagnostic().WithSpan(12, 4, 12, 61).WithSeverity(DiagnosticSeverity.Error));
}
await VerifyMauiToolkitAnalyzer(source, Diagnostic().WithSpan(12, 4, 12, 61).WithSeverity(DiagnosticSeverity.Error));
}

static Task VerifyMauiToolkitAnalyzer(string source, params IReadOnlyList<DiagnosticResult> expected)
{
return VerifyAnalyzerAsync(
source,
[
typeof(Options), // CommunityToolkit.Maui
[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<Microsoft.Maui.Controls.Application>()
#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);
}

static Task VerifyMauiToolkitAnalyzer(string source, params IReadOnlyList<DiagnosticResult> expected)
{
return VerifyAnalyzerAsync(
source,
[
typeof(Options), // CommunityToolkit.Maui
typeof(Core.Options), // CommunityToolkit.Maui.Core;
],
expected);
}
}
expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ 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";

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<DiagnosticDescriptor> SupportedDiagnostics { get; } = [rule];
Expand All @@ -32,21 +30,47 @@ 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 { Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: useMauiAppMethodName } } invocationExpression)
{
var root = invocationExpression.SyntaxTree.GetRoot();
var methodDeclaration = root.FindNode(invocationExpression.FullSpan)
var methodDeclaration = invocationExpression
.Ancestors()
.OfType<MethodDeclarationSyntax>()
.FirstOrDefault();

if (methodDeclaration is not null
&& !methodDeclaration.DescendantNodes().OfType<InvocationExpressionSyntax>().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)
{
// Ultra-efficient: search character by character without string allocations
var sourceText = methodDeclaration.SyntaxTree.GetText();
var methodSpan = methodDeclaration.Span;
var searchText = useMauiCommunityToolkitMethodName.AsSpan();

// Search through the method span character by character
for(int i = methodSpan.Start; i <= methodSpan.End - searchText.Length; i++)
{
bool found = true;
for(int j = 0; j < searchText.Length; j++)
{
if(sourceText[i + j] != searchText[j])
{
found = false;
break;
}
}
if(found)
{
return true;
}
}

return false;
}

}
Loading