Skip to content

Commit b3b2b9a

Browse files
Add UseMauiCommunityToolkit() Analyzers (#486)
* Rename `Source Generators` folder -> `Analyzers` * Add Analyzer * Update Analyzer * Add Code Fix + Unit Test * Port Unit Test to Xunit * Add Tests, Update CodeFix * Fix Typo * Update Messaging * Add Using Directive CodeFix * Update Unit Tests * Update BaseHandlerTest.cs * Fixed typo * Created a helper method to grab the InvocationExpressionSyntax * improved the code remove some comments; make SyntaxFactory static, pass the cancellationToken * Remove UseCommunityToolkitAnalyzerTests * Added a verification to check other methods in the class * Fixed typo * Added more checks to make sure we find all cases * Update UseCommunityToolkitInitializationAnalyzer.cs * Update RESX * Update azure-pipelines.yml * Update azure-pipelines.yml * Create UseCommunityToolkitInitializationAnalyzerTests.cs * Update azure-pipelines.yml * Update azure-pipelines.yml Co-authored-by: pictos <[email protected]>
1 parent 38ec66f commit b3b2b9a

20 files changed

+585
-17
lines changed

azure-pipelines.yml

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ variables:
1010
PathToCommunityToolkitCoreCsproj: 'src/CommunityToolkit.Maui.Core/CommunityToolkit.Maui.Core.csproj'
1111
PathToCommunityToolkitSampleCsproj: 'samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj'
1212
PathToCommunityToolkitUnitTestCsproj: 'src/CommunityToolkit.Maui.UnitTests/CommunityToolkit.Maui.UnitTests.csproj'
13+
PathToCommunityToolkitAnalyzersCsproj: 'src/CommunityToolkit.Maui.Analyzers/CommunityToolkit.Maui.Analyzers.csproj'
14+
PathToCommunityToolkitSourceGeneratorsCsproj: 'src/CommunityToolkit.Maui.SourceGenerators/CommunityToolkit.Maui.SourceGenerators.csproj'
15+
PathToCommunityToolkitAnalyzersCodeFixCsproj: 'src/CommunityToolkit.Maui.Analyzers.CodeFixes/CommunityToolkit.Maui.Analyzers.CodeFixes.csproj'
16+
PathToCommunityToolkitAnalyzersUnitTestCsproj: 'src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj'
1317
XcodeVersion: '13.3.1'
1418
RollbackFile: '6.0.312.json'
1519

@@ -66,14 +70,40 @@ jobs:
6670
Write-Host "##vso[build.updatebuildnumber]$fullVersionString"
6771
displayName: Set NuGet Version to PR Version
6872
condition: and(succeeded(), eq(variables['build.reason'], 'PullRequest'))
73+
# build analyzers
74+
- task: VSBuild@1
75+
displayName: 'Build CommunityToolkit.Maui.Analyzers'
76+
inputs:
77+
solution: '$(PathToCommunityToolkitAnalyzersCsproj)'
78+
configuration: 'Release'
79+
msbuildArgs: '/restore'
80+
- task: VSBuild@1
81+
displayName: 'Build CommunityToolkit.Maui.Analyzers.CodeFixes'
82+
inputs:
83+
solution: '$(PathToCommunityToolkitAnalyzersCodeFixCsproj)'
84+
configuration: 'Release'
85+
msbuildArgs: '/restore'
86+
- task: VSBuild@1
87+
displayName: 'Build CommunityToolkit.Maui.SourceGenerators'
88+
inputs:
89+
solution: '$(PathToCommunityToolkitSourceGeneratorsCsproj)'
90+
configuration: 'Release'
91+
msbuildArgs: '/restore'
6992
# test
7093
- task: DotNetCoreCLI@2
71-
displayName: 'Run Unit Tests'
94+
displayName: 'Run CommunityToolkit.Maui Unit Tests'
7295
inputs:
7396
command: 'test'
7497
projects: '$(PathToCommunityToolkitUnitTestCsproj)'
7598
arguments: '--configuration Release --settings ".runsettings" --collect "XPlat code coverage" --logger trx --results-directory $(Agent.TempDirectory)'
7699
publishTestResults: false
100+
- task: DotNetCoreCLI@2
101+
displayName: 'Run CommunityToolkit.Maui.Analyzers Unit Tests'
102+
inputs:
103+
command: 'test'
104+
projects: '$(PathToCommunityToolkitAnalyzersUnitTestCsproj)'
105+
arguments: '--configuration Release'
106+
publishTestResults: false
77107
- task: PublishTestResults@2
78108
displayName: 'Publish Test Results'
79109
inputs:
@@ -181,7 +211,23 @@ jobs:
181211
inputs:
182212
script: dotnet workload install maui --from-rollback-file $(RollbackFile) --source https://api.nuget.org/v3/index.json
183213
- task: CmdLine@2
184-
displayName: 'Run Unit Tests'
214+
displayName: 'Build CommunityToolkit.Maui.Analyzers'
215+
inputs:
216+
script: 'dotnet build -c Release $(PathToCommunityToolkitAnalyzersCsproj)'
217+
- task: CmdLine@2
218+
displayName: 'Build CommunityToolkit.Maui.Analyzers.CodeFixes'
219+
inputs:
220+
script: 'dotnet build -c Release $(PathToCommunityToolkitAnalyzersCodeFixCsproj)'
221+
- task: CmdLine@2
222+
displayName: 'Build CommunityToolkit.Maui.SourceGenerators'
223+
inputs:
224+
script: 'dotnet build -c Release $(PathToCommunityToolkitSourceGeneratorsCsproj)'
225+
- task: CmdLine@2
226+
displayName: 'Run CommunityToolkit.Maui.Analyzers.UnitTests'
227+
inputs:
228+
script: 'dotnet test $(PathToCommunityToolkitAnalyzersUnitTestCsproj) -c Release'
229+
- task: CmdLine@2
230+
displayName: 'Run CommunityToolkit.Maui.UnitTests'
185231
inputs:
186232
script: 'dotnet test $(PathToCommunityToolkitUnitTestCsproj) -c Release'
187233
- task: CmdLine@2

samples/CommunityToolkit.Maui.Sample.sln

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Core"
2020
EndProject
2121
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui", "..\src\CommunityToolkit.Maui\CommunityToolkit.Maui.csproj", "{1BA2F867-25C1-4D7E-AE51-5466929D467A}"
2222
EndProject
23-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Maui.SourceGenerators", "..\src\CommunityToolkit.Maui.SourceGenerators\CommunityToolkit.Maui.SourceGenerators.csproj", "{98B61BE8-4A47-4A56-82C1-40498DF12214}"
23+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.SourceGenerators", "..\src\CommunityToolkit.Maui.SourceGenerators\CommunityToolkit.Maui.SourceGenerators.csproj", "{98B61BE8-4A47-4A56-82C1-40498DF12214}"
2424
EndProject
25-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source Generators", "Source Generators", "{9BFC4026-BC8F-43E2-BAA9-5BC2D764D37D}"
25+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{9BFC4026-BC8F-43E2-BAA9-5BC2D764D37D}"
26+
EndProject
27+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Analyzers", "..\src\CommunityToolkit.Maui.Analyzers\CommunityToolkit.Maui.Analyzers.csproj", "{BB7A6C2F-B8F1-49FF-8E67-C436EF062A22}"
28+
EndProject
29+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Analyzers.CodeFixes", "..\src\CommunityToolkit.Maui.Analyzers.CodeFixes\CommunityToolkit.Maui.Analyzers.CodeFixes.csproj", "{20792D38-690A-4760-82FD-94E9510F86C9}"
30+
EndProject
31+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9F7D54C0-EA17-409A-804F-B2E8D7F4A7F3}"
32+
EndProject
33+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Maui.Analyzers.UnitTests", "..\src\CommunityToolkit.Maui.Analyzers.UnitTests\CommunityToolkit.Maui.Analyzers.UnitTests.csproj", "{60B976B2-F3FA-494E-A28B-7BED2EAE990E}"
2634
EndProject
2735
Global
2836
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -54,13 +62,29 @@ Global
5462
{98B61BE8-4A47-4A56-82C1-40498DF12214}.Debug|Any CPU.Build.0 = Debug|Any CPU
5563
{98B61BE8-4A47-4A56-82C1-40498DF12214}.Release|Any CPU.ActiveCfg = Release|Any CPU
5664
{98B61BE8-4A47-4A56-82C1-40498DF12214}.Release|Any CPU.Build.0 = Release|Any CPU
65+
{BB7A6C2F-B8F1-49FF-8E67-C436EF062A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66+
{BB7A6C2F-B8F1-49FF-8E67-C436EF062A22}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{BB7A6C2F-B8F1-49FF-8E67-C436EF062A22}.Release|Any CPU.ActiveCfg = Release|Any CPU
68+
{BB7A6C2F-B8F1-49FF-8E67-C436EF062A22}.Release|Any CPU.Build.0 = Release|Any CPU
69+
{20792D38-690A-4760-82FD-94E9510F86C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
70+
{20792D38-690A-4760-82FD-94E9510F86C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
71+
{20792D38-690A-4760-82FD-94E9510F86C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
72+
{20792D38-690A-4760-82FD-94E9510F86C9}.Release|Any CPU.Build.0 = Release|Any CPU
73+
{60B976B2-F3FA-494E-A28B-7BED2EAE990E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74+
{60B976B2-F3FA-494E-A28B-7BED2EAE990E}.Debug|Any CPU.Build.0 = Debug|Any CPU
75+
{60B976B2-F3FA-494E-A28B-7BED2EAE990E}.Release|Any CPU.ActiveCfg = Release|Any CPU
76+
{60B976B2-F3FA-494E-A28B-7BED2EAE990E}.Release|Any CPU.Build.0 = Release|Any CPU
5777
EndGlobalSection
5878
GlobalSection(SolutionProperties) = preSolution
5979
HideSolutionNode = FALSE
6080
EndGlobalSection
6181
GlobalSection(NestedProjects) = preSolution
82+
{7D49CC16-93CF-471B-B1FA-0BA44DECC15D} = {9F7D54C0-EA17-409A-804F-B2E8D7F4A7F3}
6283
{3A795407-604F-4502-8C76-281BE5575A84} = {AE67B86A-46F2-4078-BF02-0614B7E0E3F5}
6384
{98B61BE8-4A47-4A56-82C1-40498DF12214} = {9BFC4026-BC8F-43E2-BAA9-5BC2D764D37D}
85+
{BB7A6C2F-B8F1-49FF-8E67-C436EF062A22} = {9BFC4026-BC8F-43E2-BAA9-5BC2D764D37D}
86+
{20792D38-690A-4760-82FD-94E9510F86C9} = {9BFC4026-BC8F-43E2-BAA9-5BC2D764D37D}
87+
{60B976B2-F3FA-494E-A28B-7BED2EAE990E} = {9F7D54C0-EA17-409A-804F-B2E8D7F4A7F3}
6488
EndGlobalSection
6589
GlobalSection(ExtensibilityGlobals) = postSolution
6690
SolutionGuid = {1E9E61C1-5CB7-4C8E-87BA-6C1D38238679}

samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
<ItemGroup>
5757
<ProjectReference Include="..\..\src\CommunityToolkit.Maui\CommunityToolkit.Maui.csproj" />
5858
<ProjectReference Include="..\..\src\CommunityToolkit.Maui.SourceGenerators\CommunityToolkit.Maui.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
59+
<ProjectReference Include="..\..\src\CommunityToolkit.Maui.Analyzers\CommunityToolkit.Maui.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
60+
<ProjectReference Include="..\..\src\CommunityToolkit.Maui.Analyzers.CodeFixes\CommunityToolkit.Maui.Analyzers.CodeFixes.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
5961
</ItemGroup>
6062

6163
<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">

samples/CommunityToolkit.Maui.Sample/MauiProgram.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public static class MauiProgram
2121
public static MauiApp CreateMauiApp()
2222
{
2323
var builder = MauiApp.CreateBuilder()
24-
.UseMauiApp<App>()
2524
.UseMauiCommunityToolkit()
26-
.UseMauiCommunityToolkitMarkup();
25+
.UseMauiCommunityToolkitMarkup()
26+
.UseMauiApp<App>();
2727

2828
builder.Services.AddHttpClient<ByteArrayToImageSourceConverterViewModel>();
2929
builder.Services.AddSingleton<PopupSizeConstants>();

src/CommunityToolkit.Maui.Analyzers.CodeFixes/CodeFixResources.Designer.cs

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<resheader name="resmimetype">
4+
<value>text/microsoft-resx</value>
5+
</resheader>
6+
<resheader name="version">
7+
<value>2.0</value>
8+
</resheader>
9+
<resheader name="reader">
10+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
11+
</resheader>
12+
<resheader name="writer">
13+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
14+
</resheader>
15+
<data name="Initialize .NET MAUI Community Toolkit Before UseMauiApp" xml:space="preserve">
16+
<value>Add `.UseMauiCommunityToolkit()`</value>
17+
<comment>Add `.UseMauiCommunityToolkit()`</comment>
18+
</data>
19+
</root>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
<IsRoslynComponent>true</IsRoslynComponent>
7+
<RootNamespace>CommunityToolkit.Maui.Analyzers</RootNamespace>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.2.0" PrivateAssets="all" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\CommunityToolkit.Maui.Analyzers\CommunityToolkit.Maui.Analyzers.csproj" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<Compile Update="CodeFixResources.Designer.cs" DesignTime="True" AutoGen="True" DependentUpon="CodeFixResources.resx" />
20+
<EmbeddedResource Update="CodeFixResources.resx" Generator="ResXFileCodeGenerator" LastGenOutput="CodeFixResources.Designer.cs" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"profiles": {
3+
"CommunityToolkit.Maui.Sample": {
4+
"commandName": "DebugRoslynComponent",
5+
"targetProject": "..\\..\\samples\\CommunityToolkit.Maui.Sample\\CommunityToolkit.Maui.Sample.csproj"
6+
}
7+
}
8+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using System.Linq.Expressions;
4+
using System.Reflection.Metadata.Ecma335;
5+
using System.Runtime.InteropServices.ComTypes;
6+
using CommunityToolkit.Maui.Analyzers;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CodeActions;
9+
using Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Microsoft.CodeAnalysis.Rename;
13+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
14+
15+
namespace CommunityToolkit.Maui.Analyzers;
16+
17+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseCommunityToolkitInitializationAnalyzerCodeFixProvider)), Shared]
18+
public class UseCommunityToolkitInitializationAnalyzerCodeFixProvider : CodeFixProvider
19+
{
20+
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(UseCommunityToolkitInitializationAnalyzer.DiagnosticId);
21+
22+
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
23+
24+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
25+
{
26+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
27+
28+
var diagnostic = context.Diagnostics.First();
29+
var diagnosticSpan = diagnostic.Location.SourceSpan;
30+
31+
// Find the type declaration identified by the diagnostic.
32+
var declaration = root?.FindToken(diagnosticSpan.Start).Parent?.AncestorsAndSelf().OfType<InvocationExpressionSyntax>().Last() ?? throw new InvalidOperationException();
33+
34+
// Register a code action that will invoke the fix.
35+
context.RegisterCodeFix(
36+
CodeAction.Create(
37+
title: CodeFixResources.Initialize__NET_MAUI_Community_Toolkit_Before_UseMauiApp,
38+
createChangedDocument: c => AddUseCommunityToolkit(context.Document, declaration, c),
39+
equivalenceKey: nameof(CodeFixResources.Initialize__NET_MAUI_Community_Toolkit_Before_UseMauiApp)),
40+
diagnostic);
41+
}
42+
43+
async Task<Document> AddUseCommunityToolkit(Document document, InvocationExpressionSyntax invocationExpression, CancellationToken cancellationToken)
44+
{
45+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
46+
if (root is null)
47+
{
48+
return document;
49+
}
50+
51+
var updatedInvocationExpression =
52+
InvocationExpression(
53+
MemberAccessExpression(
54+
SyntaxKind.SimpleMemberAccessExpression, invocationExpression, IdentifierName("UseMauiCommunityToolkit")));
55+
56+
var mauiCommunityToolkitUsingStatement =
57+
UsingDirective(
58+
QualifiedName(
59+
IdentifierName("CommunityToolkit"),
60+
IdentifierName("Maui")));
61+
62+
var newRoot = root.ReplaceNode(invocationExpression, updatedInvocationExpression);
63+
64+
if (newRoot is CompilationUnitSyntax compilationSyntax)
65+
{
66+
newRoot = compilationSyntax.AddUsings(mauiCommunityToolkitUsingStatement).NormalizeWhitespace();
67+
}
68+
69+
var newDocument = document.WithSyntaxRoot(newRoot);
70+
71+
return newDocument;
72+
}
73+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
<UseMaui>true</UseMaui>
7+
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
8+
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GF</CompilerGeneratedFilesOutputPath>
9+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
10+
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="FluentAssertions" Version="6.7.0" />
15+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.1" />
16+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.1" />
17+
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
18+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
19+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
20+
<PackageReference Include="xunit" Version="2.4.1" />
21+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" PrivateAssets="All" />
22+
<PackageReference Include="coverlet.collector" Version="3.1.2" PrivateAssets="All" />
23+
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.2.0" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<ProjectReference Include="..\CommunityToolkit.Maui.Analyzers.CodeFixes\CommunityToolkit.Maui.Analyzers.CodeFixes.csproj" />
28+
</ItemGroup>
29+
30+
</Project>

0 commit comments

Comments
 (0)