Skip to content

Commit 25b7d4a

Browse files
committed
Introduce new APIs
1 parent 3f6f5b7 commit 25b7d4a

21 files changed

Lines changed: 1073 additions & 0 deletions

Directory.Build.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@
1515
<PropertyGroup>
1616
<Product>Riverside.CompilerPlatform</Product>
1717
</PropertyGroup>
18+
19+
<ItemGroup>
20+
<Compile Include="$(SourceDirectory)\GlobalUsings.cs" />
21+
</ItemGroup>
1822
</Project>

Riverside.CompilerPlatform.sln

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.CompilerPlatform.
66
EndProject
77
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.CompilerPlatform.CSharp.Tests", "tests\Riverside.CompilerPlatform.CSharp.SourceGenerators.Tests\Riverside.CompilerPlatform.CSharp.Tests.csproj", "{AA7B5994-0C82-4AB3-B3E0-2E733ADC0145}"
88
EndProject
9+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.CompilerPlatform.Analyzers", "src\Riverside.CompilerPlatform.Analyzers\Riverside.CompilerPlatform.Analyzers.csproj", "{2A4AFA3A-4D38-484C-95BE-D968C154E1EE}"
10+
EndProject
11+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.CompilerPlatform.CodeFixers", "src\Riverside.CompilerPlatform.CodeFixers\Riverside.CompilerPlatform.CodeFixers.csproj", "{21CC7F39-3FEF-4037-BBEE-BF36F1AD178E}"
12+
EndProject
13+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
14+
EndProject
15+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0FE57CFC-5B31-4BFA-8EFD-7DC4368D3C9A}"
16+
EndProject
17+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.CompilerPlatform.HighPerformance", "src\Riverside.CompilerPlatform.HighPerformance\Riverside.CompilerPlatform.HighPerformance.csproj", "{BE3A628A-7AF8-4534-A474-29B47E18936F}"
18+
EndProject
919
Global
1020
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1121
CSharp|Any CPU = CSharp|Any CPU
@@ -20,10 +30,29 @@ Global
2030
{AA7B5994-0C82-4AB3-B3E0-2E733ADC0145}.CSharp|Any CPU.Build.0 = Release|Any CPU
2131
{AA7B5994-0C82-4AB3-B3E0-2E733ADC0145}.VisualBasic|Any CPU.ActiveCfg = Release|Any CPU
2232
{AA7B5994-0C82-4AB3-B3E0-2E733ADC0145}.VisualBasic|Any CPU.Build.0 = Release|Any CPU
33+
{2A4AFA3A-4D38-484C-95BE-D968C154E1EE}.CSharp|Any CPU.ActiveCfg = CSharp|Any CPU
34+
{2A4AFA3A-4D38-484C-95BE-D968C154E1EE}.CSharp|Any CPU.Build.0 = CSharp|Any CPU
35+
{2A4AFA3A-4D38-484C-95BE-D968C154E1EE}.VisualBasic|Any CPU.ActiveCfg = Release|Any CPU
36+
{2A4AFA3A-4D38-484C-95BE-D968C154E1EE}.VisualBasic|Any CPU.Build.0 = Release|Any CPU
37+
{21CC7F39-3FEF-4037-BBEE-BF36F1AD178E}.CSharp|Any CPU.ActiveCfg = CSharp|Any CPU
38+
{21CC7F39-3FEF-4037-BBEE-BF36F1AD178E}.CSharp|Any CPU.Build.0 = CSharp|Any CPU
39+
{21CC7F39-3FEF-4037-BBEE-BF36F1AD178E}.VisualBasic|Any CPU.ActiveCfg = Release|Any CPU
40+
{21CC7F39-3FEF-4037-BBEE-BF36F1AD178E}.VisualBasic|Any CPU.Build.0 = Release|Any CPU
41+
{BE3A628A-7AF8-4534-A474-29B47E18936F}.CSharp|Any CPU.ActiveCfg = CSharp|Any CPU
42+
{BE3A628A-7AF8-4534-A474-29B47E18936F}.CSharp|Any CPU.Build.0 = CSharp|Any CPU
43+
{BE3A628A-7AF8-4534-A474-29B47E18936F}.VisualBasic|Any CPU.ActiveCfg = Release|Any CPU
44+
{BE3A628A-7AF8-4534-A474-29B47E18936F}.VisualBasic|Any CPU.Build.0 = Release|Any CPU
2345
EndGlobalSection
2446
GlobalSection(SolutionProperties) = preSolution
2547
HideSolutionNode = FALSE
2648
EndGlobalSection
49+
GlobalSection(NestedProjects) = preSolution
50+
{A478D15C-C89D-4408-AB2C-4FA0EBC01B67} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
51+
{AA7B5994-0C82-4AB3-B3E0-2E733ADC0145} = {0FE57CFC-5B31-4BFA-8EFD-7DC4368D3C9A}
52+
{2A4AFA3A-4D38-484C-95BE-D968C154E1EE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
53+
{21CC7F39-3FEF-4037-BBEE-BF36F1AD178E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
54+
{BE3A628A-7AF8-4534-A474-29B47E18936F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
55+
EndGlobalSection
2756
GlobalSection(ExtensibilityGlobals) = postSolution
2857
SolutionGuid = {FBC4E320-8D16-4BEF-BBFB-B662758E574C}
2958
EndGlobalSection

eng/LanguageInternals.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
</PropertyGroup>
55
<ItemGroup>
66
<PackageReference Include="Microsoft.CodeAnalysis.$(Configuration)" Version="4.13.0" />
7+
<PackageReference Include="Microsoft.CodeAnalysis.$(Configuration).Workspaces" Version="4.13.0" />
78
</ItemGroup>
89
</Project>

src/GlobalUsings.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
global using Microsoft.CodeAnalysis;
2+
global using Microsoft.CodeAnalysis.Diagnostics;
3+
global using Microsoft.CodeAnalysis.CodeActions;
4+
global using Microsoft.CodeAnalysis.CodeFixes;
5+
global using Microsoft.CodeAnalysis.Text;
6+
7+
#if CSHARP
8+
global using Microsoft.CodeAnalysis.CSharp;
9+
global using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
#elif VISUALBASIC
11+
global using Microsoft.CodeAnalysis.VisualBasic;
12+
global using Microsoft.CodeAnalysis.VisualBasic.Syntax;
13+
#endif
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Diagnostics;
3+
using System;
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
7+
namespace Riverside.CompilerPlatform.Analyzers;
8+
9+
/// <summary>
10+
/// Base class for analyzers that focus on a specific attribute type.
11+
/// </summary>
12+
/// <typeparam name="TAttribute">The attribute type to analyze.</typeparam>
13+
public abstract class AttributeAnalyzer<TAttribute> : DiagnosticAnalyzerEx
14+
where TAttribute : Attribute
15+
{
16+
private readonly string _attributeTypeName = typeof(TAttribute).FullName;
17+
private readonly string _attributeShortName = typeof(TAttribute).Name;
18+
19+
/// <summary>
20+
/// Gets or sets whether to also analyze subclasses of the target attribute.
21+
/// </summary>
22+
protected virtual bool IncludeAttributeSubclasses => false;
23+
24+
/// <summary>
25+
/// Gets the set of syntax kinds to register for.
26+
/// By default, registers for attributes, but can be overridden.
27+
/// </summary>
28+
protected override ImmutableArray<SyntaxKind> SyntaxKindsOfInterest =>
29+
ImmutableArray.Create(SyntaxKind.Attribute);
30+
31+
/// <summary>
32+
/// Handles syntax node analysis by filtering for attributes of the target type.
33+
/// </summary>
34+
/// <param name="context">The syntax node analysis context.</param>
35+
protected sealed override void AnalyzeNode(SyntaxNodeAnalysisContext context)
36+
{
37+
// Skip generated code
38+
if (context.IsGeneratedCode)
39+
return;
40+
41+
if (context.Node is AttributeSyntax attributeSyntax)
42+
{
43+
var semanticModel = context.SemanticModel;
44+
var attributeType = semanticModel.GetTypeInfo(attributeSyntax).Type;
45+
46+
// Check if the attribute is of the target type or a subclass
47+
bool isTargetAttribute = false;
48+
49+
if (attributeType != null)
50+
{
51+
string typeName = attributeType.ToDisplayString();
52+
53+
isTargetAttribute = typeName == _attributeTypeName ||
54+
(IncludeAttributeSubclasses && IsSubclassOfAttribute(attributeType));
55+
}
56+
else
57+
{
58+
// Try to match by name if type info is not available
59+
var name = attributeSyntax.Name.ToString();
60+
isTargetAttribute = name.EndsWith(_attributeShortName) ||
61+
name.EndsWith(_attributeShortName + "Attribute");
62+
}
63+
64+
if (isTargetAttribute)
65+
{
66+
AnalyzeAttribute(context, attributeSyntax);
67+
}
68+
}
69+
}
70+
71+
/// <summary>
72+
/// Checks if a type is a subclass of the target attribute.
73+
/// </summary>
74+
/// <param name="type">The type to check.</param>
75+
/// <returns>True if the type is a subclass of the target attribute.</returns>
76+
private bool IsSubclassOfAttribute(ITypeSymbol type)
77+
{
78+
var currentType = type;
79+
while (currentType != null)
80+
{
81+
if (currentType.ToDisplayString() == _attributeTypeName)
82+
return true;
83+
84+
currentType = currentType.BaseType;
85+
}
86+
87+
return false;
88+
}
89+
90+
/// <summary>
91+
/// Analyzes an attribute of the target type.
92+
/// </summary>
93+
/// <param name="context">The syntax node analysis context.</param>
94+
/// <param name="attributeSyntax">The attribute syntax node.</param>
95+
protected abstract void AnalyzeAttribute(
96+
SyntaxNodeAnalysisContext context,
97+
AttributeSyntax attributeSyntax);
98+
99+
/// <summary>
100+
/// Gets the attribute target symbol (the element the attribute is applied to).
101+
/// </summary>
102+
/// <param name="context">The syntax node analysis context.</param>
103+
/// <param name="attributeSyntax">The attribute syntax.</param>
104+
/// <returns>The symbol that the attribute is applied to, or null if not found.</returns>
105+
protected ISymbol? GetAttributeTarget(SyntaxNodeAnalysisContext context, AttributeSyntax attributeSyntax)
106+
{
107+
// Navigate to the element that this attribute is applied to
108+
var attributeList = attributeSyntax.Parent as AttributeListSyntax;
109+
if (attributeList == null)
110+
return null;
111+
112+
var target = attributeList.Parent;
113+
if (target == null)
114+
return null;
115+
116+
return context.SemanticModel.GetDeclaredSymbol(target);
117+
}
118+
119+
/// <summary>
120+
/// Gets the attribute data for this attribute instance.
121+
/// </summary>
122+
/// <param name="context">The syntax node analysis context.</param>
123+
/// <param name="attributeSyntax">The attribute syntax.</param>
124+
/// <returns>The attribute data, or null if not found.</returns>
125+
protected AttributeData? GetAttributeData(SyntaxNodeAnalysisContext context, AttributeSyntax attributeSyntax)
126+
{
127+
var target = GetAttributeTarget(context, attributeSyntax);
128+
if (target == null)
129+
return null;
130+
131+
// Find the attribute data that matches this attribute syntax
132+
return target.GetAttributes()
133+
.FirstOrDefault(attr =>
134+
attr.ApplicationSyntaxReference?.GetSyntax() == attributeSyntax);
135+
}
136+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Diagnostics;
3+
using System.Collections.Immutable;
4+
5+
namespace Riverside.CompilerPlatform.Analyzers;
6+
7+
/// <summary>
8+
/// Base class for diagnostic analyzers that simplifies syntax node analysis.
9+
/// </summary>
10+
public abstract class DiagnosticAnalyzerEx : DiagnosticAnalyzer
11+
{
12+
// Common analyzer properties
13+
public override abstract ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
14+
15+
// Template method pattern for customization
16+
/// <summary>
17+
/// Analyzes a syntax node for diagnostics.
18+
/// </summary>
19+
/// <param name="context">The syntax node analysis context.</param>
20+
protected abstract void AnalyzeNode(SyntaxNodeAnalysisContext context);
21+
22+
/// <summary>
23+
/// Gets the syntax kinds that this analyzer is interested in.
24+
/// </summary>
25+
protected abstract ImmutableArray<SyntaxKind> SyntaxKindsOfInterest { get; }
26+
27+
// Common initialization logic
28+
/// <summary>
29+
/// Initializes the analyzer.
30+
/// </summary>
31+
/// <param name="context">The analysis context.</param>
32+
public sealed override void Initialize(AnalysisContext context)
33+
{
34+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
35+
context.EnableConcurrentExecution();
36+
37+
// Register only for syntax nodes the derived analyzer is interested in
38+
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKindsOfInterest);
39+
}
40+
41+
// Helper methods for common analyzer patterns
42+
/// <summary>
43+
/// Creates a diagnostic at the location of the specified syntax node.
44+
/// </summary>
45+
/// <param name="descriptor">The diagnostic descriptor.</param>
46+
/// <param name="node">The syntax node where the diagnostic should be reported.</param>
47+
/// <param name="messageArgs">The message arguments.</param>
48+
/// <returns>A diagnostic instance.</returns>
49+
protected static Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor,
50+
SyntaxNode node, params object[] messageArgs)
51+
{
52+
return Diagnostic.Create(descriptor, node.GetLocation(), messageArgs);
53+
}
54+
55+
/// <summary>
56+
/// Creates a diagnostic for a specific source span.
57+
/// </summary>
58+
/// <param name="descriptor">The diagnostic descriptor.</param>
59+
/// <param name="node">The syntax node containing the span.</param>
60+
/// <param name="span">The text span within the node where the diagnostic applies.</param>
61+
/// <param name="messageArgs">The message arguments.</param>
62+
/// <returns>A diagnostic instance.</returns>
63+
protected static Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor,
64+
SyntaxNode node, TextSpan span, params object[] messageArgs)
65+
{
66+
var location = Location.Create(node.SyntaxTree, span);
67+
return Diagnostic.Create(descriptor, location, messageArgs);
68+
}
69+
70+
/// <summary>
71+
/// Creates a diagnostic for a property location.
72+
/// </summary>
73+
/// <param name="descriptor">The diagnostic descriptor.</param>
74+
/// <param name="node">The syntax node containing the property.</param>
75+
/// <param name="propertyName">The name of the property to locate.</param>
76+
/// <param name="messageArgs">The message arguments.</param>
77+
/// <returns>A diagnostic instance.</returns>
78+
protected static Diagnostic CreatePropertyDiagnostic(DiagnosticDescriptor descriptor,
79+
SyntaxNode node, string propertyName, params object[] messageArgs)
80+
{
81+
// This can be implemented based on the specific language (C# or VB)
82+
// For a proper implementation, we'd need language-specific code
83+
return CreateDiagnostic(descriptor, node, messageArgs);
84+
}
85+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace Riverside.CompilerPlatform.Analyzers.Extensibility;
7+
8+
/// <summary>
9+
/// Registry for analyzer plugins.
10+
/// </summary>
11+
public static class AnalyzerPluginRegistry
12+
{
13+
private static readonly List<IAnalyzerPlugin> _plugins = new List<IAnalyzerPlugin>();
14+
15+
/// <summary>
16+
/// Registers a plugin.
17+
/// </summary>
18+
public static void RegisterPlugin(IAnalyzerPlugin plugin)
19+
{
20+
_plugins.Add(plugin);
21+
}
22+
23+
/// <summary>
24+
/// Gets all registered analyzers.
25+
/// </summary>
26+
public static IEnumerable<DiagnosticAnalyzer> GetAllAnalyzers()
27+
{
28+
return _plugins.SelectMany(p => p.GetAnalyzers());
29+
}
30+
31+
/// <summary>
32+
/// Gets all registered code fix providers.
33+
/// </summary>
34+
public static IEnumerable<CodeFixProvider> GetAllCodeFixProviders()
35+
{
36+
return _plugins.SelectMany(p => p.GetCodeFixProviders());
37+
}
38+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Riverside.CompilerPlatform.Analyzers.Extensibility;
6+
7+
/// <summary>
8+
/// Interface for analyzer plugins.
9+
/// </summary>
10+
public interface IAnalyzerPlugin
11+
{
12+
/// <summary>
13+
/// Gets the analyzers provided by this plugin.
14+
/// </summary>
15+
IEnumerable<DiagnosticAnalyzer> GetAnalyzers();
16+
17+
/// <summary>
18+
/// Gets the code fix providers provided by this plugin.
19+
/// </summary>
20+
IEnumerable<CodeFixProvider> GetCodeFixProviders();
21+
}

0 commit comments

Comments
 (0)