Skip to content

Commit 6496bcc

Browse files
committed
Created code analyzers to detect mistakes when defining font and color providers.
1 parent 5c4947a commit 6496bcc

File tree

31 files changed

+1587
-101
lines changed

31 files changed

+1587
-101
lines changed

src/analyzers/Community.VisualStudio.Toolkit.Analyzers/Analyzers/CVST005InitializeCommandsAnalyzer.cs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ private static void CheckInitialization(State state, CompilationAnalysisContext
139139
.OrderBy((x) => x.Identifier.ValueText)
140140
.First();
141141

142-
MethodDeclarationSyntax? initializeAsyncMethod = FindInitializeAsyncMethod(packageToReport);
142+
MethodDeclarationSyntax? initializeAsyncMethod = packageToReport.FindAsyncPackageInitializeAsyncMethod();
143143

144-
foreach (INamedTypeSymbol command in state.Commands.Where((x) => !x.Value).Select((x) => x.Key))
144+
foreach (ISymbol command in state.Commands.Where((x) => !x.Value).Select((x) => x.Key))
145145
{
146146

147147
// If the commands should be initialized individually,
@@ -172,7 +172,7 @@ ConcurrentDictionary<ISymbol, bool> commands
172172
{
173173
InitializationMode? mode = null;
174174

175-
MethodDeclarationSyntax? initializeAsync = FindInitializeAsyncMethod(package);
175+
MethodDeclarationSyntax? initializeAsync = package.FindAsyncPackageInitializeAsyncMethod();
176176
if (initializeAsync is not null)
177177
{
178178
foreach (StatementSyntax statement in initializeAsync.Body.Statements)
@@ -213,22 +213,6 @@ ConcurrentDictionary<ISymbol, bool> commands
213213
return mode;
214214
}
215215

216-
internal static MethodDeclarationSyntax? FindInitializeAsyncMethod(ClassDeclarationSyntax package)
217-
{
218-
foreach (MethodDeclarationSyntax method in package.Members.OfType<MethodDeclarationSyntax>())
219-
{
220-
if (method.Modifiers.Any(SyntaxKind.ProtectedKeyword) && method.Modifiers.Any(SyntaxKind.OverrideKeyword))
221-
{
222-
if (string.Equals(method.Identifier.ValueText, "InitializeAsync"))
223-
{
224-
return method;
225-
}
226-
}
227-
}
228-
229-
return null;
230-
}
231-
232216
private enum InitializationMode
233217
{
234218
Individual,
@@ -253,7 +237,7 @@ public State(INamedTypeSymbol asyncPackageType, INamedTypeSymbol baseCommandType
253237

254238
public void Dispose()
255239
{
256-
// ConcurrentBag stores data per-thread, and thata data remains in memory
240+
// ConcurrentBag stores data per-thread, and that data remains in memory
257241
// until it is removed from the bag. Prevent a memory leak by emptying the bag.
258242
while (Packages.Count > 0)
259243
{

src/analyzers/Community.VisualStudio.Toolkit.Analyzers/Analyzers/CVST005InitializeCommandsCodeFixProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
4040
CodeAction.Create(
4141
Resources.CVST005_CodeFix,
4242
cancellation => AddInitializeAsyncMethodAsync(context.Document, declaration, cancellation),
43-
equivalenceKey: nameof(Resources.CVST001_CodeFix)
43+
equivalenceKey: nameof(Resources.CVST005_CodeFix)
4444
),
4545
diagnostic
4646
);
@@ -61,7 +61,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
6161
CodeAction.Create(
6262
Resources.CVST005_CodeFix,
6363
cancellation => InitializeSpecificCommandsAsync(context.Document, method, commandNames, cancellation),
64-
equivalenceKey: nameof(Resources.CVST001_CodeFix)
64+
equivalenceKey: nameof(Resources.CVST005_CodeFix)
6565
),
6666
diagnostic
6767
);
@@ -76,7 +76,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
7676
CodeAction.Create(
7777
Resources.CVST005_CodeFix,
7878
cancellation => InitializeAllCommandsAsync(context.Document, method, cancellation),
79-
equivalenceKey: nameof(Resources.CVST001_CodeFix)
79+
equivalenceKey: nameof(Resources.CVST005_CodeFix)
8080
),
8181
diagnostic
8282
);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Diagnostics;
3+
4+
namespace Community.VisualStudio.Toolkit.Analyzers
5+
{
6+
/// <summary>
7+
/// Detects font and color providers without an explicit GUID.
8+
/// </summary>
9+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
10+
public class CVST006SpecifyFontAndColorProviderGuidAnalyzer : MissingGuidAttributeAnalyzerBase
11+
{
12+
internal const string DiagnosticId = Diagnostics.DefineExplicitGuidForFontAndColorProviders;
13+
14+
private static readonly DiagnosticDescriptor _rule = new(
15+
DiagnosticId,
16+
GetLocalizableString(nameof(Resources.CVST006_Title)),
17+
GetLocalizableString(nameof(Resources.CVST006_MessageFormat)),
18+
"Usage",
19+
DiagnosticSeverity.Warning,
20+
isEnabledByDefault: true,
21+
description: GetLocalizableString(nameof(Resources.CVST006_Description)));
22+
23+
protected override DiagnosticDescriptor Descriptor => _rule;
24+
25+
protected override string BaseTypeName => KnownTypeNames.BaseFontAndColorProvider;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using Community.VisualStudio.Toolkit.Analyzers.Analyzers;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CodeFixes;
6+
7+
namespace Community.VisualStudio.Toolkit.Analyzers
8+
{
9+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CVST006SpecifyFontAndColorProviderGuidCodeFixProvider))]
10+
[Shared]
11+
public class CVST006SpecifyFontAndColorProviderGuidCodeFixProvider : MissingGuidAttributeCodeFixProviderBase
12+
{
13+
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(CVST006SpecifyFontAndColorProviderGuidAnalyzer.DiagnosticId);
14+
15+
protected override string Title => Resources.CVST006_CodeFix;
16+
17+
protected override string EquivalenceKey => nameof(Resources.CVST006_CodeFix);
18+
}
19+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Diagnostics;
3+
4+
namespace Community.VisualStudio.Toolkit.Analyzers
5+
{
6+
/// <summary>
7+
/// Detects font and color categories without an explicit GUID.
8+
/// </summary>
9+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
10+
public class CVST007SpecifyFontAndColorCategoryGuidAnalyzer : MissingGuidAttributeAnalyzerBase
11+
{
12+
internal const string DiagnosticId = Diagnostics.DefineExplicitGuidForFontAndColorCategories;
13+
14+
private static readonly DiagnosticDescriptor _rule = new(
15+
DiagnosticId,
16+
GetLocalizableString(nameof(Resources.CVST007_Title)),
17+
GetLocalizableString(nameof(Resources.CVST007_MessageFormat)),
18+
"Usage",
19+
DiagnosticSeverity.Warning,
20+
isEnabledByDefault: true,
21+
description: GetLocalizableString(nameof(Resources.CVST007_Description)));
22+
23+
protected override DiagnosticDescriptor Descriptor => _rule;
24+
25+
protected override string BaseTypeName => KnownTypeNames.BaseFontAndColorCategory;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using Community.VisualStudio.Toolkit.Analyzers.Analyzers;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CodeFixes;
6+
7+
namespace Community.VisualStudio.Toolkit.Analyzers
8+
{
9+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CVST007SpecifyFontAndColorCategoryGuidCodeFixProvider))]
10+
[Shared]
11+
public class CVST007SpecifyFontAndColorCategoryGuidCodeFixProvider : MissingGuidAttributeCodeFixProviderBase
12+
{
13+
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(CVST007SpecifyFontAndColorCategoryGuidAnalyzer.DiagnosticId);
14+
15+
protected override string Title => Resources.CVST007_CodeFix;
16+
17+
protected override string EquivalenceKey => nameof(Resources.CVST007_CodeFix);
18+
}
19+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System.Collections.Concurrent;
2+
using System.Collections.Immutable;
3+
using System.Linq;
4+
using System.Threading;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
10+
namespace Community.VisualStudio.Toolkit.Analyzers
11+
{
12+
/// <summary>
13+
/// Detects a missing font and color provider.
14+
/// </summary>
15+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
16+
public class CVST008FontAndColorCategoryShouldHaveProviderAnalyzer : AnalyzerBase
17+
{
18+
internal const string DiagnosticId = Diagnostics.DefineProviderForFontAndColorCategories;
19+
20+
private static readonly DiagnosticDescriptor _rule = new(
21+
DiagnosticId,
22+
GetLocalizableString(nameof(Resources.CVST008_Title)),
23+
GetLocalizableString(nameof(Resources.CVST008_MessageFormat)),
24+
"Usage",
25+
DiagnosticSeverity.Warning,
26+
isEnabledByDefault: true,
27+
description: GetLocalizableString(nameof(Resources.CVST008_Description)));
28+
29+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(_rule);
30+
31+
public override void Initialize(AnalysisContext context)
32+
{
33+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
34+
context.EnableConcurrentExecution();
35+
36+
context.RegisterCompilationStartAction(OnCompilationStart);
37+
}
38+
39+
private static void OnCompilationStart(CompilationStartAnalysisContext context)
40+
{
41+
INamedTypeSymbol baseFontAndColorProviderType;
42+
INamedTypeSymbol baseFontAndColorCategoryType;
43+
44+
baseFontAndColorProviderType = context.Compilation.GetTypeByMetadataName(KnownTypeNames.BaseFontAndColorProvider);
45+
baseFontAndColorCategoryType = context.Compilation.GetTypeByMetadataName(KnownTypeNames.BaseFontAndColorCategory);
46+
47+
if ((baseFontAndColorProviderType is not null) && (baseFontAndColorCategoryType is not null))
48+
{
49+
State state = new(baseFontAndColorProviderType, baseFontAndColorCategoryType);
50+
51+
context.RegisterSyntaxNodeAction(
52+
(x) => RecordProvidersAndCategories(state, x),
53+
SyntaxKind.ClassDeclaration
54+
);
55+
56+
context.RegisterCompilationEndAction((endContext) =>
57+
{
58+
if (state.Categories.Any() && !state.HasProviders)
59+
{
60+
// There are categories but no providers. Add a diagnostic to each category.
61+
foreach (SyntaxToken token in state.Categories.Values)
62+
{
63+
endContext.ReportDiagnostic(Diagnostic.Create(_rule, token.GetLocation()));
64+
}
65+
}
66+
});
67+
}
68+
}
69+
70+
private static void RecordProvidersAndCategories(State state, SyntaxNodeAnalysisContext context)
71+
{
72+
ClassDeclarationSyntax declaration = (ClassDeclarationSyntax)context.Node;
73+
INamedTypeSymbol classSymbol = context.SemanticModel.GetDeclaredSymbol(declaration, context.CancellationToken);
74+
75+
if (classSymbol.IsSubclassOf(state.BaseFontAndColorProviderType))
76+
{
77+
state.HasProviders = true;
78+
}
79+
else if (classSymbol.IsSubclassOf(state.BaseFontAndColorCategoryType))
80+
{
81+
state.Categories[classSymbol] = declaration.Identifier;
82+
}
83+
}
84+
85+
private class State
86+
{
87+
private int _hasProviders;
88+
89+
public State(INamedTypeSymbol baseFontAndColorProviderType, INamedTypeSymbol baseFontAndColorCategoryType)
90+
{
91+
BaseFontAndColorProviderType = baseFontAndColorProviderType;
92+
BaseFontAndColorCategoryType = baseFontAndColorCategoryType;
93+
}
94+
95+
public INamedTypeSymbol BaseFontAndColorProviderType { get; }
96+
97+
public INamedTypeSymbol BaseFontAndColorCategoryType { get; }
98+
99+
public ConcurrentDictionary<ISymbol, SyntaxToken> Categories { get; } = new();
100+
101+
public bool HasProviders
102+
{
103+
get => Interlocked.CompareExchange(ref _hasProviders, 0, 0) != 0;
104+
set => Interlocked.Exchange(ref _hasProviders, value ? 1 : 0);
105+
}
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)