Skip to content

Commit 4256862

Browse files
authored
Refactor analyzers to reduce some duplication (#205)
* Move analyzers to separate folders * Extract common code used repeatedly in code fixes * Use vendored GetBestTypeByMetadataName instead * Fixes
1 parent f34ea11 commit 4256862

25 files changed

Lines changed: 534 additions & 490 deletions

src/NetEscapades.EnumGenerators/Diagnostics/AnalyzerHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ public static class AnalyzerHelpers
99
public static (INamedTypeSymbol? enumExtensionsAttr, ExternalEnumDictionary? externalEnumTypes) GetEnumExtensionAttributes(Compilation compilation)
1010
{
1111
var enumExtensionsAttr =
12-
compilation.GetTypeByMetadataName(Attributes.EnumExtensionsAttribute);
12+
compilation.GetBestTypeByMetadataName(Attributes.EnumExtensionsAttribute);
1313
var externalEnumExtensionsAttr =
14-
compilation.GetTypeByMetadataName(Attributes.ExternalEnumExtensionsAttribute);
14+
compilation.GetBestTypeByMetadataName(Attributes.ExternalEnumExtensionsAttribute);
1515

1616
if (enumExtensionsAttr is null)
1717
{

src/NetEscapades.EnumGenerators/Diagnostics/CodeFixProviderBase.cs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,51 @@ public sealed override FixAllProvider GetFixAllProvider()
3939
.ConfigureAwait(false);
4040
},
4141

42-
HasFlagCodeFixProvider.DefaultSupportedFixAllScopes
42+
DefaultSupportedFixAllScopes
4343
);
4444
}
4545

46-
protected abstract Task<Document> FixAllAsync(
47-
Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken);
46+
protected Task<Document> FixAllAsync(
47+
Document document,
48+
ImmutableArray<Diagnostic> diagnostics,
49+
CancellationToken cancellationToken)
50+
=> FixAllAsync(document, diagnostics, FixWithEditor, cancellationToken);
51+
52+
private static async Task<Document> FixAllAsync(
53+
Document document,
54+
ImmutableArray<Diagnostic> diagnostics,
55+
Func<DocumentEditor, Diagnostic, INamedTypeSymbol, CancellationToken, Task> fixFunc,
56+
CancellationToken cancellationToken)
57+
{
58+
// Create a document editor used to apply fixes for all diagnostics
59+
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
60+
if (editor is null)
61+
{
62+
return document;
63+
}
64+
65+
foreach (var diagnostic in diagnostics)
66+
{
67+
cancellationToken.ThrowIfCancellationRequested();
68+
if (!diagnostic.Properties.TryGetValue(AnalyzerHelpers.ExtensionTypeNameProperty, out var extensionTypeName)
69+
|| extensionTypeName is null)
70+
{
71+
continue;
72+
}
73+
74+
var type = editor.SemanticModel.Compilation.GetBestTypeByMetadataName(extensionTypeName);
75+
if (type is null)
76+
{
77+
continue;
78+
}
79+
80+
await fixFunc(editor, diagnostic, type, cancellationToken);
81+
}
82+
83+
return editor.GetChangedDocument();
84+
}
85+
86+
protected abstract Task FixWithEditor(
87+
DocumentEditor editor, Diagnostic diagnostic, INamedTypeSymbol extensionTypeSymbol, CancellationToken cancellationToken);
88+
4889
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System.Diagnostics;
2+
using Microsoft.CodeAnalysis;
3+
4+
namespace NetEscapades.EnumGenerators.Diagnostics;
5+
6+
internal static class CompilationExtensions
7+
{
8+
// Copy from https://github.com/dotnet/roslyn/blob/d2ff1d83e8fde6165531ad83f0e5b1ae95908289/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs#L11-L68
9+
/// <summary>
10+
/// Gets a type by its metadata name to use for code analysis within a <see cref="Compilation"/>. This method
11+
/// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
12+
/// following rules.
13+
///
14+
/// <list type="number">
15+
/// <item><description>
16+
/// If only one type with the given name is found within the compilation and its referenced assemblies, that
17+
/// type is returned regardless of accessibility.
18+
/// </description></item>
19+
/// <item><description>
20+
/// If the current <paramref name="compilation"/> defines the symbol, that symbol is returned.
21+
/// </description></item>
22+
/// <item><description>
23+
/// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
24+
/// <paramref name="compilation"/>, that symbol is returned.
25+
/// </description></item>
26+
/// <item><description>
27+
/// Otherwise, this method returns <see langword="null"/>.
28+
/// </description></item>
29+
/// </list>
30+
/// </summary>
31+
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
32+
/// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
33+
/// <returns>The symbol to use for code analysis; otherwise, <see langword="null"/>.</returns>
34+
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
35+
{
36+
INamedTypeSymbol? type = null;
37+
38+
foreach (var currentType in compilation.GetTypesByMetadataName(fullyQualifiedMetadataName))
39+
{
40+
if (ReferenceEquals(currentType.ContainingAssembly, compilation.Assembly))
41+
{
42+
Debug.Assert(type is null);
43+
return currentType;
44+
}
45+
46+
switch (currentType.GetResultantVisibility())
47+
{
48+
case SymbolVisibility.Public:
49+
case SymbolVisibility.Internal when currentType.ContainingAssembly.GivesAccessTo(compilation.Assembly):
50+
break;
51+
52+
default:
53+
continue;
54+
}
55+
56+
if (type is object)
57+
{
58+
// Multiple visible types with the same metadata name are present
59+
return null;
60+
}
61+
62+
type = currentType;
63+
}
64+
65+
return type;
66+
}
67+
68+
// Copy from https://github.com/dotnet/roslyn/blob/d2ff1d83e8fde6165531ad83f0e5b1ae95908289/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs#L28-L73
69+
private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
70+
{
71+
// Start by assuming it's visible.
72+
var visibility = SymbolVisibility.Public;
73+
switch (symbol.Kind)
74+
{
75+
case SymbolKind.Alias:
76+
// Aliases are uber private. They're only visible in the same file that they
77+
// were declared in.
78+
return SymbolVisibility.Private;
79+
case SymbolKind.Parameter:
80+
// Parameters are only as visible as their containing symbol
81+
return GetResultantVisibility(symbol.ContainingSymbol);
82+
case SymbolKind.TypeParameter:
83+
// Type Parameters are private.
84+
return SymbolVisibility.Private;
85+
}
86+
87+
while (symbol is not null && symbol.Kind != SymbolKind.Namespace)
88+
{
89+
switch (symbol.DeclaredAccessibility)
90+
{
91+
// If we see anything private, then the symbol is private.
92+
case Accessibility.NotApplicable:
93+
case Accessibility.Private:
94+
return SymbolVisibility.Private;
95+
// If we see anything internal, then knock it down from public to
96+
// internal.
97+
case Accessibility.Internal:
98+
case Accessibility.ProtectedAndInternal:
99+
visibility = SymbolVisibility.Internal;
100+
break;
101+
// For anything else (Public, Protected, ProtectedOrInternal), the
102+
// symbol stays at the level we've gotten so far.
103+
}
104+
105+
symbol = symbol.ContainingSymbol;
106+
}
107+
108+
return visibility;
109+
}
110+
111+
private enum SymbolVisibility
112+
{
113+
Public,
114+
Internal,
115+
Private,
116+
}
117+
}

src/NetEscapades.EnumGenerators/Diagnostics/DuplicateEnumValueAnalyzer.cs renamed to src/NetEscapades.EnumGenerators/Diagnostics/DefinitionAnalyzers/DuplicateEnumValueAnalyzer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
using System.Collections.Immutable;
2-
using System.Linq;
32
using Microsoft.CodeAnalysis;
43
using Microsoft.CodeAnalysis.CSharp;
54
using Microsoft.CodeAnalysis.CSharp.Syntax;
65
using Microsoft.CodeAnalysis.Diagnostics;
76

8-
namespace NetEscapades.EnumGenerators.Diagnostics;
7+
namespace NetEscapades.EnumGenerators.Diagnostics.DefinitionAnalyzers;
98

109
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1110
public class DuplicateEnumValueAnalyzer : DiagnosticAnalyzer

src/NetEscapades.EnumGenerators/Diagnostics/DuplicateExtensionClassAnalyzer.cs renamed to src/NetEscapades.EnumGenerators/Diagnostics/DefinitionAnalyzers/DuplicateExtensionClassAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using Microsoft.CodeAnalysis;
44
using Microsoft.CodeAnalysis.Diagnostics;
55

6-
namespace NetEscapades.EnumGenerators.Diagnostics;
6+
namespace NetEscapades.EnumGenerators.Diagnostics.DefinitionAnalyzers;
77

88
[DiagnosticAnalyzer(LanguageNames.CSharp)]
99
public class DuplicateExtensionClassAnalyzer: DiagnosticAnalyzer

src/NetEscapades.EnumGenerators/Diagnostics/EnumInGenericTypeAnalyzer.cs renamed to src/NetEscapades.EnumGenerators/Diagnostics/DefinitionAnalyzers/EnumInGenericTypeAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using Microsoft.CodeAnalysis.CSharp.Syntax;
55
using Microsoft.CodeAnalysis.Diagnostics;
66

7-
namespace NetEscapades.EnumGenerators.Diagnostics;
7+
namespace NetEscapades.EnumGenerators.Diagnostics.DefinitionAnalyzers;
88

99
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1010
public class EnumInGenericTypeAnalyzer : DiagnosticAnalyzer

src/NetEscapades.EnumGenerators/Diagnostics/HasFlagCodeFixProvider.cs

Lines changed: 0 additions & 94 deletions
This file was deleted.

0 commit comments

Comments
 (0)