Skip to content

Commit b25380c

Browse files
RegistrationOptions requires a key to be set as an attribute, if it's not set it will be provided through [GenerateRegistrationOptions] if provided there
1 parent d21f232 commit b25380c

File tree

48 files changed

+660
-119
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+660
-119
lines changed

src/Dap.Shared/DebugAdapterProtocolServiceCollectionExtensions.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ internal static IContainer AddDebugAdapterProtocolInternals<T>(this IContainer c
1515
}
1616

1717
container = container.AddJsonRpcServerCore(options);
18-
container.RegisterInstanceMany(new HandlerTypeDescriptorProvider(options.Assemblies), nonPublicServiceTypes: true);
18+
19+
if (options.UseAssemblyAttributeScanning)
20+
{
21+
container.RegisterInstanceMany(new AssemblyAttributeHandlerTypeDescriptorProvider(options.Assemblies), nonPublicServiceTypes: true);
22+
}
23+
else
24+
{
25+
container.RegisterInstanceMany(new AssemblyScanningHandlerTypeDescriptorProvider(options.Assemblies), nonPublicServiceTypes: true);
26+
}
1927

2028
container.RegisterInstanceMany(options.Serializer);
2129
container.RegisterInstance(options.RequestProcessIdentifier);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Text;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using OmniSharp.Extensions.JsonRpc.Generators.Cache;
8+
9+
namespace OmniSharp.Extensions.JsonRpc.Generators
10+
{
11+
[Generator]
12+
public class AssemblyCapabilityKeyAttributeGenerator : CachedSourceGenerator<AssemblyCapabilityKeyAttributeGenerator.SyntaxReceiver, TypeDeclarationSyntax>
13+
{
14+
protected override void Execute(
15+
GeneratorExecutionContext context, SyntaxReceiver syntaxReceiver, AddCacheSource<TypeDeclarationSyntax> addCacheSource,
16+
ReportCacheDiagnostic<TypeDeclarationSyntax> cacheDiagnostic
17+
)
18+
{
19+
var namespaces = new HashSet<string>() { "OmniSharp.Extensions.LanguageServer.Protocol" };
20+
var types = syntaxReceiver.FoundNodes
21+
.Concat(syntaxReceiver.Handlers)
22+
.Select(
23+
options => {
24+
var semanticModel = context.Compilation.GetSemanticModel(options.SyntaxTree);
25+
foreach (var item in options.SyntaxTree.GetCompilationUnitRoot()
26+
.Usings
27+
.Where(z => z.Alias == null)
28+
.Select(z => z.Name.ToFullString()))
29+
{
30+
namespaces.Add(item);
31+
}
32+
33+
var typeSymbol = semanticModel.GetDeclaredSymbol(options)!;
34+
35+
return SyntaxFactory.Attribute(
36+
SyntaxFactory.IdentifierName("AssemblyCapabilityKey"), SyntaxFactory.AttributeArgumentList(
37+
SyntaxFactory.SeparatedList(
38+
new[] {
39+
SyntaxFactory.AttributeArgument(SyntaxFactory.TypeOfExpression(SyntaxFactory.ParseName(typeSymbol.ToDisplayString()))),
40+
}.Concat(options.AttributeLists.GetAttribute("CapabilityKey")!.ArgumentList!.Arguments)
41+
)
42+
)
43+
);
44+
}
45+
)
46+
.ToArray();
47+
if (types.Any())
48+
{
49+
var cu = SyntaxFactory.CompilationUnit()
50+
.WithUsings(SyntaxFactory.List(namespaces.OrderBy(z => z).Select(z => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(z)))))
51+
.AddAttributeLists(SyntaxFactory.AttributeList(target: SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword)), SyntaxFactory.SeparatedList(types)))
52+
.WithLeadingTrivia(SyntaxFactory.Comment(Preamble.GeneratedByATool))
53+
.WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed);
54+
55+
context.AddSource("AssemblyCapabilityKeys.cs", cu.NormalizeWhitespace().GetText(Encoding.UTF8));
56+
}
57+
}
58+
59+
public AssemblyCapabilityKeyAttributeGenerator() : base(() => new SyntaxReceiver(Cache))
60+
{
61+
}
62+
63+
public static CacheContainer<TypeDeclarationSyntax> Cache = new();
64+
65+
public class SyntaxReceiver : SyntaxReceiverCache<TypeDeclarationSyntax>
66+
{
67+
public List<TypeDeclarationSyntax> Handlers { get; } = new();
68+
69+
public override string? GetKey(TypeDeclarationSyntax syntax)
70+
{
71+
var hasher = new CacheKeyHasher();
72+
hasher.Append(syntax.SyntaxTree.FilePath);
73+
hasher.Append(syntax.Keyword.Text);
74+
hasher.Append(syntax.Identifier.Text);
75+
hasher.Append(syntax.TypeParameterList);
76+
hasher.Append(syntax.AttributeLists);
77+
hasher.Append(syntax.BaseList);
78+
79+
return hasher;
80+
}
81+
82+
/// <summary>
83+
/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
84+
/// </summary>
85+
public override void OnVisitNode(TypeDeclarationSyntax syntaxNode)
86+
{
87+
if (syntaxNode.Parent is TypeDeclarationSyntax) return;
88+
if (syntaxNode is ClassDeclarationSyntax or RecordDeclarationSyntax
89+
&& syntaxNode.Arity == 0
90+
&& !syntaxNode.Modifiers.Any(SyntaxKind.AbstractKeyword)
91+
&& syntaxNode.AttributeLists.ContainsAttribute("CapabilityKey")
92+
&& syntaxNode.BaseList is { } bl && bl.Types.Any(
93+
z => z.Type switch {
94+
SimpleNameSyntax { Identifier: { Text: "ICapability" }, Arity: 0 } => true,
95+
_ => false
96+
}
97+
))
98+
{
99+
Handlers.Add(syntaxNode);
100+
}
101+
}
102+
103+
public SyntaxReceiver(CacheContainer<TypeDeclarationSyntax> cache) : base(cache)
104+
{
105+
}
106+
}
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Text;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using OmniSharp.Extensions.JsonRpc.Generators.Cache;
10+
using OmniSharp.Extensions.JsonRpc.Generators.Contexts;
11+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
12+
13+
namespace OmniSharp.Extensions.JsonRpc.Generators
14+
{
15+
[Generator]
16+
public class AssemblyJsonRpcHandlersAttributeGenerator : CachedSourceGenerator<AssemblyJsonRpcHandlersAttributeGenerator.SyntaxReceiver, TypeDeclarationSyntax>
17+
{
18+
protected override void Execute(
19+
GeneratorExecutionContext context, SyntaxReceiver syntaxReceiver, AddCacheSource<TypeDeclarationSyntax> addCacheSource,
20+
ReportCacheDiagnostic<TypeDeclarationSyntax> cacheDiagnostic
21+
)
22+
{
23+
var namespaces = new HashSet<string>() { "OmniSharp.Extensions.JsonRpc" };
24+
var types = syntaxReceiver.FoundNodes
25+
.Concat(syntaxReceiver.Handlers)
26+
.Select(
27+
options => {
28+
var semanticModel = context.Compilation.GetSemanticModel(options.SyntaxTree);
29+
var typeSymbol = semanticModel.GetDeclaredSymbol(options)!;
30+
31+
return AttributeArgument(TypeOfExpression(ParseName(typeSymbol.ToDisplayString())));
32+
}
33+
)
34+
.ToArray();
35+
if (types.Any())
36+
{
37+
var cu = CompilationUnit()
38+
.WithUsings(List(namespaces.OrderBy(z => z).Select(z => UsingDirective(ParseName(z)))))
39+
.AddAttributeLists(
40+
AttributeList(
41+
target: AttributeTargetSpecifier(Token(SyntaxKind.AssemblyKeyword)),
42+
SingletonSeparatedList(Attribute(IdentifierName("AssemblyJsonRpcHandlers"), AttributeArgumentList(SeparatedList(types))))
43+
)
44+
)
45+
.WithLeadingTrivia(Comment(Preamble.GeneratedByATool))
46+
.WithTrailingTrivia(CarriageReturnLineFeed);
47+
48+
context.AddSource("AssemblyJsonRpcHandlers.cs", cu.NormalizeWhitespace().GetText(Encoding.UTF8));
49+
}
50+
}
51+
52+
public AssemblyJsonRpcHandlersAttributeGenerator() : base(() => new SyntaxReceiver(Cache))
53+
{
54+
}
55+
56+
public static CacheContainer<TypeDeclarationSyntax> Cache = new();
57+
58+
public class SyntaxReceiver : SyntaxReceiverCache<TypeDeclarationSyntax>
59+
{
60+
public List<TypeDeclarationSyntax> Handlers { get; } = new();
61+
62+
public override string? GetKey(TypeDeclarationSyntax syntax)
63+
{
64+
var hasher = new CacheKeyHasher();
65+
hasher.Append(syntax.SyntaxTree.FilePath);
66+
hasher.Append(syntax.Keyword.Text);
67+
hasher.Append(syntax.Identifier.Text);
68+
hasher.Append(syntax.TypeParameterList);
69+
hasher.Append(syntax.AttributeLists);
70+
hasher.Append(syntax.BaseList);
71+
return hasher;
72+
}
73+
74+
/// <summary>
75+
/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
76+
/// </summary>
77+
public override void OnVisitNode(TypeDeclarationSyntax syntaxNode)
78+
{
79+
if (syntaxNode.Parent is TypeDeclarationSyntax) return;
80+
if (syntaxNode is ClassDeclarationSyntax or RecordDeclarationSyntax
81+
&& syntaxNode.Arity == 0
82+
&& !syntaxNode.Modifiers.Any(SyntaxKind.AbstractKeyword)
83+
&& syntaxNode.AttributeLists.ContainsAttribute("Method")
84+
&& syntaxNode.BaseList is { } bl && bl.Types.Any(
85+
z => z.Type switch {
86+
SimpleNameSyntax { Identifier: { Text: "IJsonRpcNotificationHandler" }, Arity: 0 or 1 } => true,
87+
SimpleNameSyntax { Identifier: { Text: "IJsonRpcRequestHandler" }, Arity: 1 or 2 } => true,
88+
SimpleNameSyntax { Identifier: { Text: "IJsonRpcHandler" }, Arity: 0 } => true,
89+
_ => false
90+
}
91+
))
92+
{
93+
Handlers.Add(syntaxNode);
94+
}
95+
96+
if (syntaxNode is InterfaceDeclarationSyntax
97+
&& syntaxNode.Arity == 0
98+
&& syntaxNode.AttributeLists.ContainsAttribute("Method")
99+
&& syntaxNode.BaseList is { } bl2 && bl2.Types.Any(
100+
z => z.Type switch {
101+
SimpleNameSyntax { Identifier: { Text: "IJsonRpcNotificationHandler" }, Arity: 0 or 1 } => true,
102+
SimpleNameSyntax { Identifier: { Text: "IJsonRpcRequestHandler" }, Arity: 1 or 2 } => true,
103+
SimpleNameSyntax { Identifier: { Text: "IJsonRpcHandler" }, Arity: 0 } => true,
104+
_ => false
105+
}
106+
))
107+
{
108+
Handlers.Add(syntaxNode);
109+
}
110+
}
111+
112+
public SyntaxReceiver(CacheContainer<TypeDeclarationSyntax> cache) : base(cache)
113+
{
114+
}
115+
}
116+
}
117+
}

src/JsonRpc.Generators/Cache/CacheKeyHasher.cs

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public void Append(TypeSyntax? typeSyntax)
2929
{
3030
Append(a);
3131
}
32+
33+
if (typeSyntax is GenericNameSyntax gns)
34+
{
35+
foreach (var item in gns.TypeArgumentList.Arguments)
36+
{
37+
Append(item);
38+
}
39+
}
3240
}
3341

3442
public void Append(TypeParameterListSyntax? typeParameterListSyntax)

src/JsonRpc.Generators/Cache/CachedSourceGenerator.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public void Execute(GeneratorExecutionContext context)
5050
{
5151
context.AddSource(item.Name, item.SourceText);
5252
}
53+
5354
foreach (var item in syntaxReceiver.CachedDiagnostics)
5455
{
5556
context.ReportDiagnostic(item);
@@ -58,6 +59,8 @@ public void Execute(GeneratorExecutionContext context)
5859
syntaxReceiver.Finish(context);
5960
}
6061

61-
protected abstract void Execute(GeneratorExecutionContext context, T syntaxReceiver, AddCacheSource<TSyntax> addCacheSource, ReportCacheDiagnostic<TSyntax> cacheDiagnostic);
62+
protected abstract void Execute(
63+
GeneratorExecutionContext context, T syntaxReceiver, AddCacheSource<TSyntax> addCacheSource, ReportCacheDiagnostic<TSyntax> cacheDiagnostic
64+
);
6265
}
6366
}

src/JsonRpc.Generators/Cache/SyntaxReceiverCache.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public abstract class SyntaxReceiverCache<T> : IReceiverCache<T>, ISyntaxReceive
1414
private readonly ImmutableDictionary<string, ImmutableArray<CacheDiagnosticFactory<T>>.Builder>.Builder _foundDiagnosticFactories;
1515
private readonly List<SourceTextCache> _cachedSources = new();
1616
private readonly List<Diagnostic> _cachedDiagnostics = new();
17+
private readonly List<T> _foundNodes = new();
1718

1819
protected SyntaxReceiverCache(CacheContainer<T> cache)
1920
{
@@ -62,6 +63,7 @@ public void Finish(GeneratorExecutionContext context)
6263

6364
public IEnumerable<SourceTextCache> CachedSources => _cachedSources;
6465
public IEnumerable<Diagnostic> CachedDiagnostics => _cachedDiagnostics;
66+
public IEnumerable<T> FoundNodes => _foundNodes;
6567

6668
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
6769
{
@@ -78,10 +80,13 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
7880
{
7981
_foundDiagnosticFactories.Add(key, diagnostics.ToBuilder());
8082
_cachedDiagnostics.AddRange(diagnostics.Select(f => f(v)));
81-
return;
8283
}
8384

84-
if (_foundSourceTexts.ContainsKey(key)) return;
85+
if (_foundSourceTexts.ContainsKey(key) || _foundDiagnosticFactories.ContainsKey(key))
86+
{
87+
_foundNodes.Add(v);
88+
return;
89+
}
8590
}
8691

8792
OnVisitNode(v);

src/JsonRpc.Generators/Contexts/GeneratorData.cs

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ abstract record GeneratorData(
1919
SyntaxSymbol? Capability,
2020
SyntaxSymbol? RegistrationOptions,
2121
HashSet<string> AdditionalUsings,
22+
List<AttributeArgumentSyntax> AssemblyJsonRpcHandlersAttributeArguments,
2223
SemanticModel Model,
2324
GeneratorExecutionContext Context
2425
)
@@ -73,6 +74,7 @@ HashSet<string> additionalUsings
7374
GetPartialItem(candidateClass, symbol, requestType),
7475
GetPartialItems(candidateClass, symbol, requestType),
7576
additionalUsings,
77+
new List<AttributeArgumentSyntax>(),
7678
model,
7779
context
7880
) { CacheDiagnosticDelegate = cacheDiagnostic, AddCacheSourceDelegate = addCacheSource };
@@ -90,6 +92,7 @@ HashSet<string> additionalUsings
9092
GetCapability(candidateClass, symbol, lspAttributes),
9193
GetRegistrationOptions(candidateClass, symbol, lspAttributes),
9294
additionalUsings,
95+
new List<AttributeArgumentSyntax>(),
9396
model,
9497
context
9598
) { CacheDiagnosticDelegate = cacheDiagnostic, AddCacheSourceDelegate = addCacheSource };

src/JsonRpc.Generators/Contexts/NotificationItem.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ record NotificationItem(
1414
SyntaxSymbol? Capability,
1515
SyntaxSymbol? RegistrationOptions,
1616
HashSet<string> AdditionalUsings,
17+
List<AttributeArgumentSyntax> AssemblyJsonRpcHandlersAttributeArguments,
1718
SemanticModel Model,
1819
GeneratorExecutionContext Context
1920
) : GeneratorData(
2021
TypeDeclaration, TypeSymbol, JsonRpcAttributes, LspAttributes,
2122
DapAttributes, Request, Capability, RegistrationOptions,
22-
AdditionalUsings, Model, Context
23+
AdditionalUsings, AssemblyJsonRpcHandlersAttributeArguments, Model, Context
2324
);
2425
}

src/JsonRpc.Generators/Contexts/RegistrationOptionAttributes.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ namespace OmniSharp.Extensions.JsonRpc.Generators.Contexts
66
{
77
record RegistrationOptionAttributes(
88
SyntaxAttributeData? GenerateRegistrationOptions,
9-
string? ServerCapabilityKey,
9+
string? Key,
10+
ExpressionSyntax? KeyExpression,
1011
bool SupportsWorkDoneProgress,
1112
bool SupportsDocumentSelector,
1213
bool SupportsStaticRegistrationOptions,
@@ -81,14 +82,17 @@ bool ImplementsStaticRegistrationOptions
8182
}
8283

8384
string? value = null;
85+
ExpressionSyntax? valueSyntax = null;
8486
if (data is { ConstructorArguments: { Length: > 0 } arguments } && arguments[0].Kind is TypedConstantKind.Primitive && arguments[0].Value is string)
8587
{
8688
value = arguments[0].Value as string;
89+
valueSyntax = attributeSyntax.ArgumentList!.Arguments[0].Expression;
8790
}
8891

8992
return new RegistrationOptionAttributes(
9093
new SyntaxAttributeData(attributeSyntax, data),
9194
value,
95+
valueSyntax,
9296
supportsWorkDoneProgress,
9397
supportsDocumentSelector,
9498
supportsStaticRegistrationOptions,

src/JsonRpc.Generators/Contexts/RequestItem.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ record RequestItem(
1818
SyntaxSymbol? PartialItem,
1919
SyntaxSymbol? PartialItems,
2020
HashSet<string> AdditionalUsings,
21+
List<AttributeArgumentSyntax> AssemblyJsonRpcHandlersAttributeArguments,
2122
SemanticModel Model,
2223
GeneratorExecutionContext Context
2324
) : GeneratorData(
2425
TypeDeclaration, TypeSymbol,
2526
JsonRpcAttributes, LspAttributes, DapAttributes, Request, Capability, RegistrationOptions,
26-
AdditionalUsings, Model, Context
27+
AdditionalUsings, AssemblyJsonRpcHandlersAttributeArguments, Model, Context
2728
);
2829

2930
// record PartialItem(TypeSyntax Syntax, INamedTypeSymbol Symbol, SyntaxSymbol Item) : SyntaxSymbol(Syntax, Symbol);

0 commit comments

Comments
 (0)