diff --git a/Directory.Build.targets b/Directory.Build.targets index 7694e4abe..6eda0850d 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,52 +1,49 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LSP.sln b/LSP.sln index 110faead4..14e66c9f4 100644 --- a/LSP.sln +++ b/LSP.sln @@ -14,7 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2F323ED5-E EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".config", ".config", "{AE4D7807-6F78-428C-A0D9-914BA583A104}" - ProjectSection(SolutionItems) = prePrlaoject + ProjectSection(SolutionItems) = preProject .appveyor.yml = .appveyor.yml .editorconfig = .editorconfig .gitattributes = .gitattributes diff --git a/README.md b/README.md index 33100a9de..26af571e1 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ For more information about using the `DebugAdapterClient` / `DebugAdapterServer` | OmniSharp.Extensions.DebugAdapter.Shared | [![nuget-version-2fkn0yzdbhmg-badge]![nuget-downloads-2fkn0yzdbhmg-badge]][nuget-2fkn0yzdbhmg] | | OmniSharp.Extensions.DebugAdapter.Testing | [![nuget-version-jppuysmkpfcw-badge]![nuget-downloads-jppuysmkpfcw-badge]][nuget-jppuysmkpfcw] | | OmniSharp.Extensions.JsonRpc | [![nuget-version-a1bmkwyotvkg-badge]![nuget-downloads-a1bmkwyotvkg-badge]][nuget-a1bmkwyotvkg] | +| OmniSharp.Extensions.JsonRpc.Generators | [![nuget-version-m6majhsjiw1q-badge]![nuget-downloads-m6majhsjiw1q-badge]][nuget-m6majhsjiw1q] | | OmniSharp.Extensions.JsonRpc.Testing | [![nuget-version-punkj7/efvjq-badge]![nuget-downloads-punkj7/efvjq-badge]][nuget-punkj7/efvjq] | | OmniSharp.Extensions.LanguageClient | [![nuget-version-fclou9t/p2ba-badge]![nuget-downloads-fclou9t/p2ba-badge]][nuget-fclou9t/p2ba] | | OmniSharp.Extensions.LanguageProtocol | [![nuget-version-vddj9t6jnirq-badge]![nuget-downloads-vddj9t6jnirq-badge]][nuget-vddj9t6jnirq] | @@ -118,6 +119,9 @@ This project is supported by the [.NET Foundation](http://www.dotnetfoundation.o [nuget-a1bmkwyotvkg]: https://www.nuget.org/packages/OmniSharp.Extensions.JsonRpc/ [nuget-version-a1bmkwyotvkg-badge]: https://img.shields.io/nuget/v/OmniSharp.Extensions.JsonRpc.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" [nuget-downloads-a1bmkwyotvkg-badge]: https://img.shields.io/nuget/dt/OmniSharp.Extensions.JsonRpc.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" +[nuget-m6majhsjiw1q]: https://www.nuget.org/packages/OmniSharp.Extensions.JsonRpc.Generators/ +[nuget-version-m6majhsjiw1q-badge]: https://img.shields.io/nuget/v/OmniSharp.Extensions.JsonRpc.Generators.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" +[nuget-downloads-m6majhsjiw1q-badge]: https://img.shields.io/nuget/dt/OmniSharp.Extensions.JsonRpc.Generators.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" [nuget-punkj7/efvjq]: https://www.nuget.org/packages/OmniSharp.Extensions.JsonRpc.Testing/ [nuget-version-punkj7/efvjq-badge]: https://img.shields.io/nuget/v/OmniSharp.Extensions.JsonRpc.Testing.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" [nuget-downloads-punkj7/efvjq-badge]: https://img.shields.io/nuget/dt/OmniSharp.Extensions.JsonRpc.Testing.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" diff --git a/src/Dap.Protocol/Dap.Protocol.csproj b/src/Dap.Protocol/Dap.Protocol.csproj index f922cb5cd..40417c6b1 100644 --- a/src/Dap.Protocol/Dap.Protocol.csproj +++ b/src/Dap.Protocol/Dap.Protocol.csproj @@ -14,8 +14,7 @@ - - + <_Parameter1>OmniSharp.Extensions.DebugAdapter.Server, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f diff --git a/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs b/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs index a207514ae..99739c072 100644 --- a/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs +++ b/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs @@ -1,130 +1,254 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; -using CodeGeneration.Roslyn; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static OmniSharp.Extensions.JsonRpc.Generators.Helpers; namespace OmniSharp.Extensions.JsonRpc.Generators { - public class GenerateHandlerMethodsGenerator : IRichCodeGenerator + [Generator] + public class GenerateHandlerMethodsGenerator : ISourceGenerator { - private readonly AttributeData _attributeData; - - public GenerateHandlerMethodsGenerator(AttributeData attributeData) => _attributeData = attributeData; + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } - public Task GenerateRichAsync(TransformationContext context, IProgress progress, CancellationToken cancellationToken) + public void Execute(GeneratorExecutionContext context) { - if (!( context.ProcessingNode is InterfaceDeclarationSyntax handlerInterface )) + if (!( context.SyntaxReceiver is SyntaxReceiver syntaxReceiver )) { - return Task.FromResult(new RichGenerationResult()); + return; } - var methods = new List(); - var additionalUsings = new HashSet { - "System", - "System.Collections.Generic", - "System.Threading", - "System.Threading.Tasks", - "MediatR", - "Microsoft.Extensions.DependencyInjection", - }; - var symbol = context.SemanticModel.GetDeclaredSymbol(handlerInterface); + var options = ( context.Compilation as CSharpCompilation )?.SyntaxTrees[0].Options as CSharpParseOptions; + var compilation = context.Compilation; + + var generateHandlerMethodsAttributeSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.JsonRpc.Generation.GenerateHandlerMethodsAttribute"); + var generateRequestMethodsAttributeSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.JsonRpc.Generation.GenerateRequestMethodsAttribute"); - var className = GetExtensionClassName(symbol); +// context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.Message, null, $"generateHandlerMethodsAttributeSymbol: {generateHandlerMethodsAttributeSymbol.ToDisplayString()}")); +// context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.Message, null, $"generateRequestMethodsAttributeSymbol: {generateRequestMethodsAttributeSymbol.ToDisplayString()}")); - foreach (var registry in GetRegistries(_attributeData, handlerInterface, symbol, context, progress, additionalUsings)) + foreach (var candidateClass in syntaxReceiver.Candidates) + { +// context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.Message, null, $"candidate: {candidateClass.Identifier.ToFullString()}")); + // can this be async??? + context.CancellationToken.ThrowIfCancellationRequested(); + + var model = compilation.GetSemanticModel(candidateClass.SyntaxTree); + var symbol = model.GetDeclaredSymbol(candidateClass); + + var methods = new List(); + var additionalUsings = new HashSet { + "System", + "System.Collections.Generic", + "System.Threading", + "System.Threading.Tasks", + "MediatR", + "Microsoft.Extensions.DependencyInjection", + }; + +// context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.Message, null, $"candidate: {candidateClass.Identifier.ToFullString()}")); + + var attribute = symbol?.GetAttributes().FirstOrDefault(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, generateHandlerMethodsAttributeSymbol)); + if (attribute != null) + { + GetExtensionHandlers( + context, + candidateClass, + symbol, + attribute, + methods, + additionalUsings + ); + } + + attribute = symbol?.GetAttributes().FirstOrDefault(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, generateRequestMethodsAttributeSymbol)); + if (attribute != null) + { + GetExtensionRequestHandlers( + context, + candidateClass, + symbol, + attribute, + methods, + additionalUsings + ); + } + + if (!methods.Any()) continue; + + var className = GetExtensionClassName(symbol); + + var existingUsings = candidateClass.SyntaxTree.GetCompilationUnitRoot() + .Usings + .Select(x => x.WithoutTrivia()) + .Union( + additionalUsings + .Except( + candidateClass.SyntaxTree.GetCompilationUnitRoot() + .Usings.Select(z => z.Name.ToFullString()) + ) + .Distinct() + .Select(z => UsingDirective(IdentifierName(z))) + ) + .OrderBy(x => x.Name.ToFullString()) + .ToImmutableArray(); + + var obsoleteAttribute = candidateClass.AttributeLists + .SelectMany(z => z.Attributes) + .Where(z => z.Name.ToFullString() == nameof(ObsoleteAttribute) || z.Name.ToFullString() == "Obsolete") + .ToArray(); + var attributes = List( + new[] { + AttributeList( + SeparatedList( + new[] { + Attribute(ParseName("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")), + Attribute(ParseName("System.Runtime.CompilerServices.CompilerGeneratedAttribute")), + }.Union(obsoleteAttribute) + ) + ) + } + ); + + var isInternal = candidateClass.Modifiers.Any(z => z.IsKind(SyntaxKind.InternalKeyword)); + + var cu = CompilationUnit( + List(), + List(existingUsings), + List(), + List( + new[] { + NamespaceDeclaration(ParseName(symbol.ContainingNamespace.ToDisplayString())) + .WithMembers( + SingletonList( + ClassDeclaration(className) + .WithAttributeLists(attributes) + .WithModifiers( + TokenList( + new[] { isInternal ? Token(SyntaxKind.InternalKeyword) : Token(SyntaxKind.PublicKeyword) }.Concat( + new[] { + Token(SyntaxKind.StaticKeyword), + Token(SyntaxKind.PartialKeyword) + } + ) + ) + ) + .WithMembers(List(methods)) + .WithLeadingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))) + .WithTrailingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))) + .NormalizeWhitespace() + ) + ) + } + ) + ) + .WithLeadingTrivia(Comment(Preamble.GeneratedByATool)) + .WithTrailingTrivia(CarriageReturnLineFeed) + .NormalizeWhitespace(); + + context.AddSource( + $"JsonRpc_Handlers_{candidateClass.Identifier.ToFullString().Replace(".", "_")}.cs", + cu.SyntaxTree.GetRoot().GetText(Encoding.UTF8) + ); + } + } + + + private void GetExtensionHandlers( + GeneratorExecutionContext context, + TypeDeclarationSyntax handlerClassOrInterface, + INamedTypeSymbol symbol, + AttributeData attributeData, + List methods, + HashSet additionalUsings + ) + { + foreach (var registry in GetRegistries(attributeData, handlerClassOrInterface, symbol, context, additionalUsings)) { if (IsNotification(symbol)) { var requestType = GetRequestType(symbol); - methods.AddRange(HandleNotifications(handlerInterface, symbol, requestType, registry, additionalUsings)); + methods.AddRange(HandleNotifications(handlerClassOrInterface, symbol, requestType, registry, additionalUsings, attributeData)); } else if (IsRequest(symbol)) { var requestType = GetRequestType(symbol); - var responseType = GetResponseType(handlerInterface); - methods.AddRange(HandleRequest(handlerInterface, symbol, requestType, responseType!, registry, additionalUsings)); + var responseType = GetResponseType(handlerClassOrInterface); + methods.AddRange(HandleRequest(handlerClassOrInterface, symbol, requestType, responseType!, registry, additionalUsings, attributeData)); } } + } - var existingUsings = context.CompilationUnitUsings - .Join(additionalUsings, z => z.Name.ToFullString(), z => z, (a, b) => b) - ; - - var newUsings = additionalUsings - .Except(existingUsings) - .Select(z => UsingDirective(IdentifierName(z))) - ; + private void GetExtensionRequestHandlers( + GeneratorExecutionContext context, + TypeDeclarationSyntax handlerClassOrInterface, + INamedTypeSymbol symbol, + AttributeData attributeData, + List methods, + HashSet additionalUsings + ) + { + var registries = GetProxies( + context, + attributeData, + handlerClassOrInterface, + symbol, + methods, + additionalUsings + ); - var obsoleteAttribute = handlerInterface.AttributeLists - .SelectMany(z => z.Attributes) - .Where(z => z.Name.ToFullString() == nameof(ObsoleteAttribute) || z.Name.ToFullString() == "Obsolete") - .ToArray(); - var attributes = List( - new[] { - AttributeList( - SeparatedList( - new[] { - Attribute(ParseName("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")), - Attribute(ParseName("System.Runtime.CompilerServices.CompilerGeneratedAttribute")), - }.Union(obsoleteAttribute) - ) + if (( attributeData.ConstructorArguments.Length == 0 || + ( attributeData.ConstructorArguments[0].Kind != TypedConstantKind.Array && attributeData.ConstructorArguments[0].Value == null ) + || ( attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Array && attributeData.ConstructorArguments[0].Values.Length == 0 ) ) + && !symbol.ContainingNamespace.ToDisplayString().StartsWith("OmniSharp.Extensions.DebugAdapter.Protocol")) + { + context.ReportDiagnostic( + Diagnostic.Create( + GeneratorDiagnostics.NoResponseRouterProvided, handlerClassOrInterface.Identifier.GetLocation(), symbol.Name, + string.Join(", ", registries.Select(z => z.ToFullString())) ) + ); + } + + foreach (var registry in registries) + { + if (IsNotification(symbol)) + { + var requestType = GetRequestType(symbol); + methods.AddRange(HandleRequestNotifications(handlerClassOrInterface, symbol, requestType, registry, additionalUsings, attributeData)); } - ); - var isInternal = handlerInterface.Modifiers.Any(z => z.IsKind(SyntaxKind.InternalKeyword)); - - return Task.FromResult( - new RichGenerationResult { - Usings = List(newUsings), - Members = List( - new[] { - NamespaceDeclaration(ParseName(symbol.ContainingNamespace.ToDisplayString())) - .WithMembers( - List( - new MemberDeclarationSyntax[] { - ClassDeclaration(className) - .WithAttributeLists(attributes) - .WithModifiers( - TokenList( - new[] { isInternal ? Token(SyntaxKind.InternalKeyword) : Token(SyntaxKind.PublicKeyword) }.Concat( - new[] { - Token(SyntaxKind.StaticKeyword), - Token(SyntaxKind.PartialKeyword) - } - ) - ) - ) - .WithMembers(List(methods)) - .WithLeadingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))) - .WithTrailingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))) - .NormalizeWhitespace() - } - ) - ) - } - ) + if (IsRequest(symbol)) + { + var requestType = GetRequestType(symbol); + var responseType = GetResponseType(handlerClassOrInterface); + methods.AddRange(HandleRequestRequests(handlerClassOrInterface, symbol, requestType, responseType!, registry, additionalUsings, attributeData)); } - ); + } } private IEnumerable HandleNotifications( - InterfaceDeclarationSyntax handlerInterface, + TypeDeclarationSyntax handlerInterface, INamedTypeSymbol interfaceType, INamedTypeSymbol requestType, NameSyntax registryType, - HashSet additionalUsings + HashSet additionalUsings, + AttributeData attributeData ) { - var methodName = GetOnMethodName(interfaceType, _attributeData); + var methodName = GetOnMethodName(interfaceType, attributeData); var parameters = ParameterList( SeparatedList( @@ -223,15 +347,16 @@ MemberDeclarationSyntax MakeAction(TypeSyntax syntax) } private IEnumerable HandleRequest( - InterfaceDeclarationSyntax handlerInterface, + TypeDeclarationSyntax handlerInterface, INamedTypeSymbol interfaceType, INamedTypeSymbol requestType, TypeSyntax responseType, NameSyntax registryType, - HashSet additionalUsings + HashSet additionalUsings, + AttributeData attributeData ) { - var methodName = GetOnMethodName(interfaceType, _attributeData); + var methodName = GetOnMethodName(interfaceType, attributeData); var capability = GetCapability(interfaceType); var registrationOptions = GetRegistrationOptions(interfaceType); @@ -252,10 +377,10 @@ HashSet additionalUsings ) ); - var allowDerivedRequests = _attributeData.NamedArguments - .Where(z => z.Key == "AllowDerivedRequests") - .Select(z => z.Value.Value) - .FirstOrDefault() is bool b && b; + var allowDerivedRequests = attributeData.NamedArguments + .Where(z => z.Key == "AllowDerivedRequests") + .Select(z => z.Value.Value) + .FirstOrDefault() is bool b && b; if (registrationOptions == null) @@ -344,7 +469,9 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, false)); if (capability != null) { - method = method.WithExpressionBody(GetPartialResultCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, partialTypeSyntax, responseType, capability)); + method = method.WithExpressionBody( + GetPartialResultCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, partialTypeSyntax, responseType, capability) + ); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, capability)); } } @@ -358,6 +485,7 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) { method = method.WithExpressionBody(GetVoidRequestCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, capability)); } + yield return MakeAction(CreateAsyncFunc(responseType, requestType, capability)); } } @@ -456,7 +584,9 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) { var partialTypeSyntax = ResolveTypeName(partialItem); - method = method.WithBody(GetPartialResultRegistrationHandlerExpression(GetMethodName(handlerInterface), requestType, partialTypeSyntax, responseType, registrationOptions)); + method = method.WithBody( + GetPartialResultRegistrationHandlerExpression(GetMethodName(handlerInterface), requestType, partialTypeSyntax, responseType, registrationOptions) + ); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, true)); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, false)); @@ -489,14 +619,18 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) private static IEnumerable GetRegistries( AttributeData attributeData, - InterfaceDeclarationSyntax interfaceSyntax, + TypeDeclarationSyntax interfaceSyntax, INamedTypeSymbol interfaceType, - TransformationContext context, - IProgress progress, + GeneratorExecutionContext context, HashSet additionalUsings ) { - if (attributeData.ConstructorArguments[0].Values.Length > 0) + if (attributeData.ConstructorArguments[0].Kind != TypedConstantKind.Array) + { + if (attributeData.ConstructorArguments[0].Value is INamedTypeSymbol namedTypeSymbol) + return new[] { ResolveTypeName(namedTypeSymbol) }; + } + else if (attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Array && attributeData.ConstructorArguments[0].Values.Length > 0) { return attributeData.ConstructorArguments[0].Values.Select(z => z.Value).OfType() .Select(ResolveTypeName); @@ -507,7 +641,7 @@ HashSet additionalUsings var attribute = interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute"); if (attribute.ConstructorArguments.Length < 2) { - progress.Report(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); return Enumerable.Empty(); } @@ -526,13 +660,13 @@ HashSet additionalUsings { additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client"); additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities"); - return new[] { LanguageProtocolServerToClient }; + return new[] { LanguageProtocolServerToClientRegistry }; } if (maskedDirection == 2) { additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Server"); - return new[] { LanguageProtocolClientToServer }; + return new[] { LanguageProtocolClientToServerRegistry }; } if (maskedDirection == 3) @@ -540,7 +674,7 @@ HashSet additionalUsings additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client"); additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities"); additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Server"); - return new[] { LanguageProtocolClientToServer, LanguageProtocolServerToClient }; + return new[] { LanguageProtocolClientToServerRegistry, LanguageProtocolServerToClientRegistry }; } } @@ -549,7 +683,7 @@ HashSet additionalUsings var attribute = interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute"); if (attribute.ConstructorArguments.Length < 2) { - progress.Report(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); return Enumerable.Empty(); } @@ -566,6 +700,117 @@ HashSet additionalUsings additionalUsings.Add("OmniSharp.Extensions.DebugAdapter.Protocol.Client"); additionalUsings.Add("OmniSharp.Extensions.DebugAdapter.Protocol.Server"); + if (maskedDirection == 1) + { + return new[] { DebugProtocolServerToClientRegistry }; + } + + if (maskedDirection == 2) + { + return new[] { DebugProtocolClientToServerRegistry }; + } + + if (maskedDirection == 3) + { + return new[] { DebugProtocolClientToServerRegistry, DebugProtocolServerToClientRegistry }; + } + } + + throw new NotImplementedException("Add inference logic here " + interfaceSyntax.Identifier.ToFullString()); + } + + private static NameSyntax LanguageProtocolServerToClientRegistry { get; } = + IdentifierName("ILanguageClientRegistry"); + + private static NameSyntax LanguageProtocolClientToServerRegistry { get; } = + IdentifierName("ILanguageServerRegistry"); + + private static NameSyntax DebugProtocolServerToClientRegistry { get; } = + IdentifierName("IDebugAdapterClientRegistry"); + + private static NameSyntax DebugProtocolClientToServerRegistry { get; } = + IdentifierName("IDebugAdapterServerRegistry"); + + + public static IEnumerable GetProxies( + GeneratorExecutionContext context, + AttributeData attributeData, + TypeDeclarationSyntax interfaceSyntax, + INamedTypeSymbol interfaceType, + List methods, + HashSet additionalUsings + ) + { + if (attributeData.ConstructorArguments[0].Kind != TypedConstantKind.Array) + { + if (attributeData.ConstructorArguments[0].Value is INamedTypeSymbol namedTypeSymbol) + return new[] { ResolveTypeName(namedTypeSymbol) }; + } + else if (attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Array && attributeData.ConstructorArguments[0].Values.Length > 0) + { + return attributeData.ConstructorArguments[0].Values.Select(z => z.Value).OfType() + .Select(ResolveTypeName); + } + + if (interfaceType.ContainingNamespace.ToDisplayString().StartsWith("OmniSharp.Extensions.LanguageServer.Protocol")) + { + var attribute = interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute"); + if (attribute.ConstructorArguments.Length < 2) + { + context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); + return Enumerable.Empty(); + } + + var direction = (int) interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute").ConstructorArguments[1].Value; + + /* + Unspecified = 0b0000, + ServerToClient = 0b0001, + ClientToServer = 0b0010, + Bidirectional = 0b0011 + */ + var maskedDirection = 0b0011 & direction; + + if (maskedDirection == 1) + { + additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Server"); + return new[] { LanguageProtocolServerToClient }; + } + + if (maskedDirection == 2) + { + additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client"); + return new[] { LanguageProtocolClientToServer }; + } + + if (maskedDirection == 3) + { + additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Server"); + additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client"); + return new[] { LanguageProtocolClientToServer, LanguageProtocolServerToClient }; + } + } + + if (interfaceType.ContainingNamespace.ToDisplayString().StartsWith("OmniSharp.Extensions.DebugAdapter.Protocol")) + { + var attribute = interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute"); + if (attribute.ConstructorArguments.Length < 2) + { + context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); + return Enumerable.Empty(); + } + + var direction = (int) interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute").ConstructorArguments[1].Value; + + /* + Unspecified = 0b0000, + ServerToClient = 0b0001, + ClientToServer = 0b0010, + Bidirectional = 0b0011 + */ + var maskedDirection = 0b0011 & direction; + additionalUsings.Add("OmniSharp.Extensions.DebugAdapter.Protocol"); + if (maskedDirection == 1) { return new[] { DebugProtocolServerToClient }; @@ -586,24 +831,199 @@ HashSet additionalUsings } private static NameSyntax LanguageProtocolServerToClient { get; } = - IdentifierName("ILanguageClientRegistry"); + ParseName("ILanguageServer"); private static NameSyntax LanguageProtocolClientToServer { get; } = - IdentifierName("ILanguageServerRegistry"); + ParseName("ILanguageClient"); private static NameSyntax DebugProtocolServerToClient { get; } = - IdentifierName("IDebugAdapterClientRegistry"); + ParseName("IDebugAdapterServer"); private static NameSyntax DebugProtocolClientToServer { get; } = - IdentifierName("IDebugAdapterServerRegistry"); + ParseName("IDebugAdapterClient"); - public Task> GenerateAsync(TransformationContext context, IProgress progress, CancellationToken cancellationToken) => - throw new NotImplementedException(); - } + private IEnumerable HandleRequestNotifications( + TypeDeclarationSyntax handlerInterface, + INamedTypeSymbol interfaceType, + INamedTypeSymbol requestType, + NameSyntax registryType, + HashSet additionalUsings, + AttributeData attributeData + ) + { + var methodName = GetSendMethodName(interfaceType, attributeData); + var method = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), methodName) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword), + Token(SyntaxKind.StaticKeyword) + ) + ) + .WithExpressionBody(GetNotificationInvokeExpression()) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + + yield return method + .WithParameterList( + ParameterList( + SeparatedList( + new[] { + Parameter(Identifier("mediator")) + .WithType(registryType) + .WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword))), + Parameter(Identifier("@params")) + .WithType(IdentifierName(requestType.Name)) + } + ) + ) + ) + .NormalizeWhitespace(); + } + + private IEnumerable HandleRequestRequests( + TypeDeclarationSyntax handlerInterface, + INamedTypeSymbol interfaceType, + INamedTypeSymbol requestType, + TypeSyntax responseType, + NameSyntax registryType, + HashSet additionalUsings, + AttributeData attributeData + ) + { + var methodName = GetSendMethodName(interfaceType, attributeData); + var parameterList = ParameterList( + SeparatedList( + new[] { + Parameter(Identifier("mediator")) + .WithType(registryType) + .WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword))), + Parameter(Identifier("@params")) + .WithType(IdentifierName(requestType.Name)), + Parameter(Identifier("cancellationToken")) + .WithType(IdentifierName("CancellationToken")) + .WithDefault( + EqualsValueClause( + LiteralExpression(SyntaxKind.DefaultLiteralExpression, Token(SyntaxKind.DefaultKeyword)) + ) + ) + } + ) + ); + var partialItem = GetPartialItem(requestType); + if (partialItem != null) + { + additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Progress"); + var partialTypeSyntax = ResolveTypeName(partialItem); + yield return MethodDeclaration( + GenericName( + Identifier("IRequestProgressObservable") + ) + .WithTypeArgumentList( + TypeArgumentList( + SeparatedList( + new TypeSyntax[] { + partialTypeSyntax, + responseType + } + ) + ) + ), + Identifier(methodName) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword), + Token(SyntaxKind.StaticKeyword) + ) + ) + .WithParameterList(parameterList) + .WithExpressionBody(GetPartialInvokeExpression(responseType)) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .NormalizeWhitespace(); + yield break; + } + + var partialItems = GetPartialItems(requestType); + if (partialItems != null) + { + additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Progress"); + var partialTypeSyntax = ResolveTypeName(partialItems); + var partialItemsSyntax = GenericName("IEnumerable").WithTypeArgumentList(TypeArgumentList(SeparatedList(new[] { partialTypeSyntax }))); + yield return MethodDeclaration( + GenericName( + Identifier("IRequestProgressObservable") + ) + .WithTypeArgumentList( + TypeArgumentList( + SeparatedList( + new TypeSyntax[] { + partialItemsSyntax, + responseType + } + ) + ) + ), + Identifier(methodName) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword), + Token(SyntaxKind.StaticKeyword) + ) + ) + .WithParameterList(parameterList) + .WithExpressionBody(GetPartialInvokeExpression(responseType)) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .NormalizeWhitespace(); + ; + yield break; + } + + + var responseSyntax = responseType.ToFullString().EndsWith("Unit") + ? IdentifierName("Task") as NameSyntax + : GenericName("Task").WithTypeArgumentList(TypeArgumentList(SeparatedList(new[] { responseType }))); + yield return MethodDeclaration(responseSyntax, methodName) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword), + Token(SyntaxKind.StaticKeyword) + ) + ) + .WithParameterList(parameterList) + .WithExpressionBody(GetRequestInvokeExpression()) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .NormalizeWhitespace(); + } - /* - IJsonRpcNotificationHandler - IJsonRpcRequestHandler - IJsonRpcRequestHandler - */ + + /// + /// Created on demand before each generation pass + /// + internal class SyntaxReceiver : ISyntaxReceiver + { + public List Candidates { get; } = new List(); + + /// + /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + /// + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + // any field with at least one attribute is a candidate for property generation + if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax && + classDeclarationSyntax.AttributeLists.Count > 0 + ) + { + Candidates.Add(classDeclarationSyntax); + } + + // any field with at least one attribute is a candidate for property generation + if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && + interfaceDeclarationSyntax.AttributeLists.Count > 0 + ) + { + Candidates.Add(interfaceDeclarationSyntax); + } + } + } + } } diff --git a/src/JsonRpc.Generators/GenerateRequestMethodsGenerator.cs b/src/JsonRpc.Generators/GenerateRequestMethodsGenerator.cs deleted file mode 100644 index 44d90907d..000000000 --- a/src/JsonRpc.Generators/GenerateRequestMethodsGenerator.cs +++ /dev/null @@ -1,393 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using CodeGeneration.Roslyn; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -using static OmniSharp.Extensions.JsonRpc.Generators.Helpers; - -namespace OmniSharp.Extensions.JsonRpc.Generators -{ - public class GenerateRequestMethodsGenerator : IRichCodeGenerator - { - private readonly AttributeData _attributeData; - - public GenerateRequestMethodsGenerator(AttributeData attributeData) => _attributeData = attributeData; - - public Task> GenerateAsync(TransformationContext context, IProgress progress, CancellationToken cancellationToken) => - throw new NotImplementedException(); - - public Task GenerateRichAsync(TransformationContext context, IProgress progress, CancellationToken cancellationToken) - { - if (!( context.ProcessingNode is InterfaceDeclarationSyntax handlerInterface )) - { - return Task.FromResult(new RichGenerationResult()); - } - - var methods = new List(); - var additionalUsings = new HashSet { - "System", - "System.Collections.Generic", - "System.Threading", - "System.Threading.Tasks", - "MediatR", - "Microsoft.Extensions.DependencyInjection", - }; - var symbol = context.SemanticModel.GetDeclaredSymbol(handlerInterface); - - var className = GetExtensionClassName(symbol); - - var registries = GetProxies(_attributeData, handlerInterface, symbol, context, progress, additionalUsings); - - if (_attributeData.ConstructorArguments[0].Values.Length == 0 && !symbol.ContainingNamespace.ToDisplayString().StartsWith("OmniSharp.Extensions.DebugAdapter.Protocol")) - { - progress.Report( - Diagnostic.Create( - GeneratorDiagnostics.NoResponseRouterProvided, handlerInterface.Identifier.GetLocation(), symbol.Name, - string.Join(", ", registries.Select(z => z.ToFullString())) - ) - ); - } - - foreach (var registry in registries) - { - if (IsNotification(symbol)) - { - var requestType = GetRequestType(handlerInterface); - methods.AddRange(HandleNotifications(handlerInterface, symbol, requestType, registry, additionalUsings)); - } - - if (IsRequest(symbol)) - { - var requestType = GetRequestType(handlerInterface); - var responseType = GetResponseType(handlerInterface); - methods.AddRange(HandleRequests(handlerInterface, symbol, requestType, responseType!, registry, additionalUsings)); - } - } - - - var obsoleteAttribute = handlerInterface.AttributeLists - .SelectMany(z => z.Attributes) - .Where(z => z.Name.ToFullString() == nameof(ObsoleteAttribute) || z.Name.ToFullString() == "Obsolete") - .ToArray(); - var attributes = List( - new[] { - AttributeList( - SeparatedList( - new[] { - Attribute(ParseName("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")), - Attribute(ParseName("System.Runtime.CompilerServices.CompilerGeneratedAttribute")), - }.Union(obsoleteAttribute) - ) - ) - } - ); - if (symbol.GetAttributes().Any(z => z.AttributeClass.Name == "GenerateRequestMethodsAttribute")) - { - attributes = List(); - } - - var existingUsings = context.CompilationUnitUsings - .Join(additionalUsings, z => z.Name.ToFullString(), z => z, (a, b) => b) - ; - - var newUsings = additionalUsings - .Except(existingUsings) - .Select(z => UsingDirective(IdentifierName(z))) - ; - var isInternal = handlerInterface.Modifiers.Any(z => z.IsKind(SyntaxKind.InternalKeyword)); - return Task.FromResult( - new RichGenerationResult { - Usings = List(newUsings), - Members = List( - new[] { - NamespaceDeclaration(ParseName(symbol.ContainingNamespace.ToDisplayString())) - .WithMembers( - List( - new MemberDeclarationSyntax[] { - ClassDeclaration(className) - .WithAttributeLists(attributes) - .WithModifiers( - TokenList( - new[] { isInternal ? Token(SyntaxKind.InternalKeyword) : Token(SyntaxKind.PublicKeyword) }.Concat( - new[] { - Token(SyntaxKind.StaticKeyword), - Token(SyntaxKind.PartialKeyword) - } - ) - ) - ) - .WithMembers(List(methods)) - .WithLeadingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))) - .WithTrailingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))) - .NormalizeWhitespace() - } - ) - ) - } - ) - } - ); - } - - public static IEnumerable GetProxies( - AttributeData attributeData, - InterfaceDeclarationSyntax interfaceSyntax, - INamedTypeSymbol interfaceType, - TransformationContext context, - IProgress progress, - HashSet additionalUsings - ) - { - if (attributeData.ConstructorArguments[0].Values.Length > 0) - { - return attributeData.ConstructorArguments[0].Values.Select(z => z.Value).OfType() - .Select(ResolveTypeName); - } - - if (interfaceType.ContainingNamespace.ToDisplayString().StartsWith("OmniSharp.Extensions.LanguageServer.Protocol")) - { - var attribute = interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute"); - if (attribute.ConstructorArguments.Length < 2) - { - progress.Report(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); - return Enumerable.Empty(); - } - - var direction = (int) interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute").ConstructorArguments[1].Value!; - - /* - Unspecified = 0b0000, - ServerToClient = 0b0001, - ClientToServer = 0b0010, - Bidirectional = 0b0011 - */ - var maskedDirection = 0b0011 & direction; - - if (maskedDirection == 1) - { - additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Server"); - return new[] { LanguageProtocolServerToClient }; - } - - if (maskedDirection == 2) - { - additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client"); - return new[] { LanguageProtocolClientToServer }; - } - - if (maskedDirection == 3) - { - additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Server"); - additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Client"); - return new[] { LanguageProtocolClientToServer, LanguageProtocolServerToClient }; - } - } - - if (interfaceType.ContainingNamespace.ToDisplayString().StartsWith("OmniSharp.Extensions.DebugAdapter.Protocol")) - { - var attribute = interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute"); - if (attribute.ConstructorArguments.Length < 2) - { - progress.Report(Diagnostic.Create(GeneratorDiagnostics.MissingDirection, interfaceSyntax.Identifier.GetLocation())); - return Enumerable.Empty(); - } - - var direction = (int) interfaceType.GetAttributes().First(z => z.AttributeClass?.Name == "MethodAttribute").ConstructorArguments[1].Value!; - - /* - Unspecified = 0b0000, - ServerToClient = 0b0001, - ClientToServer = 0b0010, - Bidirectional = 0b0011 - */ - var maskedDirection = 0b0011 & direction; - additionalUsings.Add("OmniSharp.Extensions.DebugAdapter.Protocol"); - - if (maskedDirection == 1) - { - return new[] { DebugProtocolServerToClient }; - } - - if (maskedDirection == 2) - { - return new[] { DebugProtocolClientToServer }; - } - - if (maskedDirection == 3) - { - return new[] { DebugProtocolClientToServer, DebugProtocolServerToClient }; - } - } - - throw new NotImplementedException("Add inference logic here " + interfaceSyntax.Identifier.ToFullString()); - } - - private static NameSyntax LanguageProtocolServerToClient { get; } = - ParseName("ILanguageServer"); - - private static NameSyntax LanguageProtocolClientToServer { get; } = - ParseName("ILanguageClient"); - - private static NameSyntax DebugProtocolServerToClient { get; } = - ParseName("IDebugAdapterServer"); - - private static NameSyntax DebugProtocolClientToServer { get; } = - ParseName("IDebugAdapterClient"); - - private IEnumerable HandleNotifications( - InterfaceDeclarationSyntax handlerInterface, - INamedTypeSymbol interfaceType, - TypeSyntax requestType, - NameSyntax registryType, - HashSet additionalUsings - ) - { - var methodName = GetSendMethodName(interfaceType, _attributeData); - var method = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), methodName) - .WithModifiers( - TokenList( - Token(SyntaxKind.PublicKeyword), - Token(SyntaxKind.StaticKeyword) - ) - ) - .WithExpressionBody(GetNotificationInvokeExpression()) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); - - yield return method - .WithParameterList( - ParameterList( - SeparatedList( - new[] { - Parameter(Identifier("mediator")) - .WithType(registryType) - .WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword))), - Parameter(Identifier("@params")) - .WithType(requestType) - } - ) - ) - ) - .NormalizeWhitespace(); - } - - private IEnumerable HandleRequests( - InterfaceDeclarationSyntax handlerInterface, - INamedTypeSymbol interfaceType, - TypeSyntax requestType, - TypeSyntax responseType, - NameSyntax registryType, - HashSet additionalUsings - ) - { - var methodName = GetSendMethodName(interfaceType, _attributeData); - var parameterList = ParameterList( - SeparatedList( - new[] { - Parameter(Identifier("mediator")) - .WithType(registryType) - .WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword))), - Parameter(Identifier("@params")) - .WithType(requestType), - Parameter(Identifier("cancellationToken")) - .WithType(IdentifierName("CancellationToken")) - .WithDefault( - EqualsValueClause( - LiteralExpression(SyntaxKind.DefaultLiteralExpression, Token(SyntaxKind.DefaultKeyword)) - ) - ) - } - ) - ); - var partialItem = GetPartialItem(GetRequestType(interfaceType)); - if (partialItem != null) - { - additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Progress"); - var partialTypeSyntax = ResolveTypeName(partialItem); - yield return MethodDeclaration( - GenericName( - Identifier("IRequestProgressObservable") - ) - .WithTypeArgumentList( - TypeArgumentList( - SeparatedList( - new TypeSyntax[] { - partialTypeSyntax, - responseType - } - ) - ) - ), - Identifier(methodName) - ) - .WithModifiers( - TokenList( - Token(SyntaxKind.PublicKeyword), - Token(SyntaxKind.StaticKeyword) - ) - ) - .WithParameterList(parameterList) - .WithExpressionBody(GetPartialInvokeExpression(responseType)) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) - .NormalizeWhitespace(); - yield break; - } - - var partialItems = GetPartialItems(GetRequestType(interfaceType)); - if (partialItems != null) - { - additionalUsings.Add("OmniSharp.Extensions.LanguageServer.Protocol.Progress"); - var partialTypeSyntax = ResolveTypeName(partialItems); - var partialItemsSyntax = GenericName("IEnumerable").WithTypeArgumentList(TypeArgumentList(SeparatedList(new[] { partialTypeSyntax }))); - yield return MethodDeclaration( - GenericName( - Identifier("IRequestProgressObservable") - ) - .WithTypeArgumentList( - TypeArgumentList( - SeparatedList( - new TypeSyntax[] { - partialItemsSyntax, - responseType - } - ) - ) - ), - Identifier(methodName) - ) - .WithModifiers( - TokenList( - Token(SyntaxKind.PublicKeyword), - Token(SyntaxKind.StaticKeyword) - ) - ) - .WithParameterList(parameterList) - .WithExpressionBody(GetPartialInvokeExpression(responseType)) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) - .NormalizeWhitespace(); - - yield break; - } - - - var responseSyntax = responseType.ToFullString().EndsWith("Unit") - ? IdentifierName("Task") as NameSyntax - : GenericName("Task").WithTypeArgumentList(TypeArgumentList(SeparatedList(new[] { responseType }))); - yield return MethodDeclaration(responseSyntax, methodName) - .WithModifiers( - TokenList( - Token(SyntaxKind.PublicKeyword), - Token(SyntaxKind.StaticKeyword) - ) - ) - .WithParameterList(parameterList) - .WithExpressionBody(GetRequestInvokeExpression()) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) - .NormalizeWhitespace(); - } - } -} diff --git a/src/JsonRpc.Generators/GeneratorDiagnostics.cs b/src/JsonRpc.Generators/GeneratorDiagnostics.cs index 76b4f835a..3642d47d0 100644 --- a/src/JsonRpc.Generators/GeneratorDiagnostics.cs +++ b/src/JsonRpc.Generators/GeneratorDiagnostics.cs @@ -6,17 +6,36 @@ internal static class GeneratorDiagnostics { public static DiagnosticDescriptor MissingDirection { get; } = new DiagnosticDescriptor( "LSP1000", "Missing Direction", - "No direction defined for Language Server Protocol Handler", "JsonRPC", DiagnosticSeverity.Warning, true + "No direction defined for Language Server Protocol Handler", "LSP", DiagnosticSeverity.Warning, true + ); + public static DiagnosticDescriptor Exception { get; } = new DiagnosticDescriptor( + "JRPC0001", "Exception", + "{0}", "JRPC", DiagnosticSeverity.Error, true ); public static DiagnosticDescriptor NoHandlerRegistryProvided { get; } = new DiagnosticDescriptor( "JRPC1000", "No Handler Registry Provided", - "No Handler Registry Provided for handler {0}.", "JsonRPC", DiagnosticSeverity.Warning, true + "No Handler Registry Provided for handler {0}.", "JsonRPC", DiagnosticSeverity.Info, true ); public static DiagnosticDescriptor NoResponseRouterProvided { get; } = new DiagnosticDescriptor( "JRPC1001", "No Response Router Provided", - "No Response Router Provided for handler {0}, defaulting to {1}.", "JsonRPC", DiagnosticSeverity.Warning, true + "No Response Router Provided for handler {0}, defaulting to {1}.", "JsonRPC", DiagnosticSeverity.Info, true + ); + + public static DiagnosticDescriptor ClassMustBePartial { get; } = new DiagnosticDescriptor( + "JRPC1002", "Class must be made partial", + "Class {0} must be made partial.", "JsonRPC", DiagnosticSeverity.Warning, true + ); + + public static DiagnosticDescriptor MustInheritFromCanBeResolved { get; } = new DiagnosticDescriptor( + "LSP1001", "The target class must implement ICanBeResolved", + "The target class must implement ICanBeResolved", "LSP", DiagnosticSeverity.Error, true + ); + + public static DiagnosticDescriptor MustInheritFromCanHaveData { get; } = new DiagnosticDescriptor( + "LSP1002", "The target class must implement ICanHaveData", + "The target class must implement ICanHaveData", "LSP", DiagnosticSeverity.Error, true ); } } diff --git a/src/JsonRpc.Generators/Helpers.cs b/src/JsonRpc.Generators/Helpers.cs index a74c98e65..7aab668bd 100644 --- a/src/JsonRpc.Generators/Helpers.cs +++ b/src/JsonRpc.Generators/Helpers.cs @@ -16,7 +16,7 @@ internal static class Helpers public static bool IsRequest(INamedTypeSymbol symbol) => symbol.AllInterfaces.Any(z => z.Name == "IJsonRpcRequestHandler"); - public static ExpressionSyntax GetMethodName(InterfaceDeclarationSyntax interfaceSyntax) + public static ExpressionSyntax GetMethodName(TypeDeclarationSyntax interfaceSyntax) { var methodAttribute = interfaceSyntax.AttributeLists .SelectMany(z => z.Attributes) diff --git a/src/JsonRpc.Generators/JsonRpc.Generators.csproj b/src/JsonRpc.Generators/JsonRpc.Generators.csproj index 3a1bf25f0..e9cdf72c9 100644 --- a/src/JsonRpc.Generators/JsonRpc.Generators.csproj +++ b/src/JsonRpc.Generators/JsonRpc.Generators.csproj @@ -1,17 +1,25 @@ - - - netstandard2.0 + netstandard2.0 OmniSharp.Extensions.JsonRpc.Generators OmniSharp.Extensions.JsonRpc.Generators - false + true + + + - - + + + + <_Parameter1>Generation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100391db875e68eb4bfef49ce14313b9e13f2cd3cc89eb273bbe6c11a55044c7d4f566cf092e1c77ef9e7c75b1496ae7f95d925938f5a01793dd8d9f99ae0a7595779b71b971287d7d7b5960d052078d14f5ce1a85ea5c9fb2f59ac735ff7bc215cab469b7c3486006860bad6f4c3b5204ea2f28dd4e1d05e2cca462cfd593b9f9f + + + + + diff --git a/src/JsonRpc.Generators/Preamble.cs b/src/JsonRpc.Generators/Preamble.cs new file mode 100644 index 000000000..cf24c3b87 --- /dev/null +++ b/src/JsonRpc.Generators/Preamble.cs @@ -0,0 +1,16 @@ +using System; + +namespace OmniSharp.Extensions.JsonRpc.Generators +{ + public static class Preamble { + /// + /// A "generated by tool" comment string with environment/os-normalized newlines. + /// + public static readonly string GeneratedByATool = @"// ------------------------------------------------------------------------------ +// +// This code was generated a code generator. +// +// ------------------------------------------------------------------------------ +".Replace("\r\n", "\n").Replace("\n", Environment.NewLine); // normalize regardless of git checkout policy + } +} diff --git a/src/JsonRpc.Generators/RegistrationOptionsGenerator.cs b/src/JsonRpc.Generators/RegistrationOptionsGenerator.cs new file mode 100644 index 000000000..90bb604e8 --- /dev/null +++ b/src/JsonRpc.Generators/RegistrationOptionsGenerator.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using SyntaxTrivia = Microsoft.CodeAnalysis.SyntaxTrivia; + +namespace OmniSharp.Extensions.JsonRpc.Generators +{ + [Generator] + public class RegistrationOptionsGenerator : ISourceGenerator + { + private static string[] RequiredUsings = new[] { + "OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities", + "OmniSharp.Extensions.LanguageServer.Protocol", + }; + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + if (!( context.SyntaxReceiver is SyntaxReceiver syntaxReceiver )) + { + return; + } + + var options = ( context.Compilation as CSharpCompilation )?.SyntaxTrees[0].Options as CSharpParseOptions; + var compilation = context.Compilation; + + var registrationOptionsAttribute = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.RegistrationOptionsAttribute")!; + var textDocumentAttributeSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.TextDocumentAttribute")!; + var workDoneProgressAttributeSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.WorkDoneProgressAttribute")!; + var registrationOptionsConverterAttributeSymbol = + compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.RegistrationOptionsConverterAttribute")!; + var registrationOptionsInterfaceSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.IRegistrationOptions")!; + var textDocumentRegistrationOptionsInterfaceSymbol = + compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.Models.ITextDocumentRegistrationOptions")!; + var workDoneProgressOptionsInterfaceSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.Models.IWorkDoneProgressOptions")!; + + foreach (var registrationOptions in syntaxReceiver.RegistrationOptions) + { + var semanticModel = context.Compilation.GetSemanticModel(registrationOptions.SyntaxTree); + var typeSymbol = semanticModel.GetDeclaredSymbol(registrationOptions); + var hasAttribute = typeSymbol?.GetAttributes().Any(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, registrationOptionsAttribute)); + if (typeSymbol == null || typeSymbol.IsAbstract || hasAttribute != true) continue; + + var converterAttribute = typeSymbol.GetAttributes() + .FirstOrDefault(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, registrationOptionsConverterAttributeSymbol)); + + var extendedRegistrationOptions = registrationOptions + .WithAttributeLists(List()) + .WithBaseList( + BaseList( + SingletonSeparatedList( + SimpleBaseType(ParseName(registrationOptionsInterfaceSymbol.ToDisplayString())) + ) + ) + ) + .WithMembers(List()); + + var staticRegistrationOptions = registrationOptions + .WithIdentifier(Identifier($"Static{registrationOptions.Identifier.Text}")) + .WithMembers(List(registrationOptions.Members.OfType())) + .WithAttributeLists(List()); + + if (typeSymbol.GetAttributes().Any(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, textDocumentAttributeSymbol))) + { + extendedRegistrationOptions = ExtendAndImplementInterface(extendedRegistrationOptions, textDocumentRegistrationOptionsInterfaceSymbol) + .AddMembers( + PropertyDeclaration(NullableType(IdentifierName("DocumentSelector")), Identifier("DocumentSelector")) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) + .WithAccessorList( + AccessorList( + List( + new[] { + AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), + AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + } + ) + ) + ) + ); + } + + if (typeSymbol.GetAttributes().Any(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, workDoneProgressAttributeSymbol))) + { + extendedRegistrationOptions = ExtendAndImplementInterface(extendedRegistrationOptions, workDoneProgressOptionsInterfaceSymbol) + .AddMembers(GetWorkDoneProperty()); + staticRegistrationOptions = ExtendAndImplementInterface(staticRegistrationOptions, workDoneProgressOptionsInterfaceSymbol) + .AddMembers(GetWorkDoneProperty()); + } + + if (converterAttribute == null) + { + var converter = CreateConverter(registrationOptions); + extendedRegistrationOptions = extendedRegistrationOptions + .AddAttributeLists( + AttributeList( + SingletonSeparatedList( + Attribute( + IdentifierName("RegistrationOptionsConverterAttribute"), + AttributeArgumentList( + SingletonSeparatedList( + AttributeArgument( + TypeOfExpression(IdentifierName(converter.Identifier.Text)) + ) + ) + ) + ) + ) + ) + ) + .AddMembers(converter); + } + + var cu = CompilationUnit() + .WithUsings(registrationOptions.SyntaxTree.GetCompilationUnitRoot().Usings) + .AddMembers( + NamespaceDeclaration(ParseName(typeSymbol.ContainingNamespace.ToDisplayString())) + .AddMembers(extendedRegistrationOptions, staticRegistrationOptions) + .WithLeadingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))) + .WithTrailingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))) + ) + .WithLeadingTrivia(Comment(Preamble.GeneratedByATool)) + .WithTrailingTrivia(CarriageReturnLineFeed); + + foreach (var ns in RequiredUsings) + { + if (cu.Usings.All(z => z.Name.ToFullString() != ns)) + { + cu = cu.AddUsings(UsingDirective(ParseName(ns))); + } + } + + context.AddSource( + $"{registrationOptions.Identifier.Text}Container.cs", + cu.NormalizeWhitespace().SyntaxTree.GetRoot().GetText(Encoding.UTF8) + ); + } + + static ClassDeclarationSyntax ExtendAndImplementInterface(ClassDeclarationSyntax syntax, ITypeSymbol symbolToExtendFrom) + { + return syntax + .AddBaseListTypes(SimpleBaseType(ParseName(symbolToExtendFrom.Name))); + } + + static PropertyDeclarationSyntax GetWorkDoneProperty() + { + return PropertyDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)), Identifier("WorkDoneProgress")) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) + .WithAttributeLists( + SingletonList( + AttributeList( + SingletonSeparatedList(Attribute(IdentifierName("Optional"))) + ) + ) + ) + .WithAccessorList( + AccessorList( + List( + new[] { + AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), + AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + } + ) + ) + ); + } + } + + private static IEnumerable GetMapping(ClassDeclarationSyntax syntax, IdentifierNameSyntax paramName) + { + return syntax.Members.OfType() + .Where(z => z.AccessorList?.Accessors.Any(a => a.Keyword.Kind() == SyntaxKind.SetKeyword || a.Keyword.Kind() == SyntaxKind.InitKeyword) == true) + .Select( + property => AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName(property.Identifier.Text), + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + paramName, + IdentifierName(property.Identifier.Text) + ) + ) + ); + } + + private ClassDeclarationSyntax CreateConverter(ClassDeclarationSyntax syntax) + { + var attribute = syntax.AttributeLists + .SelectMany(z => z.Attributes) + .FirstOrDefault(z => z.Name.ToFullString().Contains("RegistrationOptions") && !z.Name.ToFullString().Contains("RegistrationOptionsConverter")); + + return ClassDeclaration($"{syntax.Identifier.Text}Converter") + .WithBaseList( + BaseList( + SingletonSeparatedList( + SimpleBaseType( + GenericName(Identifier("RegistrationOptionsConverterBase")) + .WithTypeArgumentList( + TypeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] { + IdentifierName(syntax.Identifier.Text), + Token(SyntaxKind.CommaToken), + IdentifierName($"Static{syntax.Identifier.Text}") + } + ) + ) + ) + ) + ) + ) + ) + .WithMembers( + List( + new MemberDeclarationSyntax[] { + ConstructorDeclaration(Identifier("Converter")) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) + .WithInitializer( + ConstructorInitializer( + SyntaxKind.BaseConstructorInitializer, + ArgumentList(SingletonSeparatedList(Argument(attribute!.ArgumentList!.Arguments[0].Expression))) + ) + ) + .WithBody(Block()), + MethodDeclaration(IdentifierName($"Static{syntax.Identifier.Text}"), Identifier("Convert")) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) + .WithParameterList( + ParameterList( + SingletonSeparatedList( + Parameter(Identifier("source")) + .WithType(IdentifierName(syntax.Identifier.Text)) + ) + ) + ) + .WithBody( + Block( + SingletonList( + ReturnStatement( + ObjectCreationExpression( + IdentifierName($"Static{syntax.Identifier.Text}") + ) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + GetMapping(syntax, IdentifierName("source")).ToArray() + ) + ) + ) + ) + ) + ) + ) + } + ) + ); + } + + /// + /// Created on demand before each generation pass + /// + internal class SyntaxReceiver : ISyntaxReceiver + { + public List RegistrationOptions { get; } = new(); + + /// + /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + /// + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + // any field with at least one attribute is a candidate for property generation + if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax) + { + if (classDeclarationSyntax.AttributeLists + .SelectMany(z => z.Attributes) + .Any(z => z.Name.ToFullString().Contains("RegistrationOptions")) + ) + { + RegistrationOptions.Add(classDeclarationSyntax); + } + } + } + } + } +} diff --git a/src/JsonRpc.Generators/StronglyTypedGenerator.cs b/src/JsonRpc.Generators/StronglyTypedGenerator.cs new file mode 100644 index 000000000..5b53c71af --- /dev/null +++ b/src/JsonRpc.Generators/StronglyTypedGenerator.cs @@ -0,0 +1,872 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using SyntaxTrivia = Microsoft.CodeAnalysis.SyntaxTrivia; + +namespace OmniSharp.Extensions.JsonRpc.Generators +{ + [Generator] + public class StronglyTypedGenerator : ISourceGenerator + { + private static string[] RequiredUsings = new[] { + "System.Collections.Generic", + "System.Collections.ObjectModel", + "System.Collections.Immutable", + "System.Linq", + }; + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + if (!( context.SyntaxReceiver is SyntaxReceiver syntaxReceiver )) + { + return; + } + + var options = ( context.Compilation as CSharpCompilation )?.SyntaxTrees[0].Options as CSharpParseOptions; + var compilation = context.Compilation; + + var canBeResolvedSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.Models.ICanBeResolved"); + var canHaveDataSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.Models.ICanHaveData"); + var generateTypedDataAttributeSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.GenerateTypedDataAttribute"); + var generateContainerAttributeSymbol = compilation.GetTypeByMetadataName("OmniSharp.Extensions.LanguageServer.Protocol.GenerateContainerAttribute"); + var requestSymbol = compilation.GetTypeByMetadataName("MediatR.IRequest"); + + foreach (var classToContain in syntaxReceiver.CreateContainers) + { + var semanticModel = context.Compilation.GetSemanticModel(classToContain.SyntaxTree); + var typeSymbol = semanticModel.GetDeclaredSymbol(classToContain); + var hasAttribute = typeSymbol?.GetAttributes().Any(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, generateContainerAttributeSymbol)); + if (typeSymbol == null || hasAttribute != true) continue; + + var container = CreateContainerClass(classToContain) + .AddAttributeLists( + AttributeList( + SeparatedList( + new[] { + Attribute(ParseName("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")), + Attribute(ParseName("System.Runtime.CompilerServices.CompilerGeneratedAttribute")) + } + ) + ) + ); + + var cu = CompilationUnit() + .WithUsings(classToContain.SyntaxTree.GetCompilationUnitRoot().Usings) + .AddMembers( + NamespaceDeclaration(ParseName(typeSymbol.ContainingNamespace.ToDisplayString())) + .AddMembers(container) + .WithLeadingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))) + .WithTrailingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))) + ) + .WithLeadingTrivia(Comment(Preamble.GeneratedByATool)) + .WithTrailingTrivia(CarriageReturnLineFeed); + + foreach (var ns in RequiredUsings) + { + if (cu.Usings.All(z => z.Name.ToFullString() != ns)) + { + cu = cu.AddUsings(UsingDirective(ParseName(ns))); + } + } + + context.AddSource( + $"{classToContain.Identifier.Text}Container.cs", + cu.NormalizeWhitespace().SyntaxTree.GetRoot().GetText(Encoding.UTF8) + ); + } + + foreach (var canBeResolved in syntaxReceiver.CanBeResolved) + { + var semanticModel = context.Compilation.GetSemanticModel(canBeResolved.SyntaxTree); + var typeSymbol = semanticModel.GetDeclaredSymbol(canBeResolved); + if (typeSymbol == null) continue; + var attribute = typeSymbol?.GetAttributes().FirstOrDefault(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, generateTypedDataAttributeSymbol)); + var isContainer = typeSymbol?.GetAttributes().Any(z => SymbolEqualityComparer.Default.Equals(z.AttributeClass, generateContainerAttributeSymbol)) == true; + if (attribute == null) continue; + + if (!canBeResolved.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.ClassMustBePartial, canBeResolved.Identifier.GetLocation())); + } + + var property = canBeResolved.Members.OfType().Single(z => z.Identifier.Text == "Data"); + var dataInterfaceName = IdentifierName("ICanBeResolved"); + var partialClass = canBeResolved + .WithAttributeLists(List()) + .WithBaseList(null) + .WithMembers(List()) + .AddMembers(GetWithDataMethod(canBeResolved, TypeConstraint(NullableType(IdentifierName("HandlerIdentity"))))); + + var compilationMembers = new List() { + }; + + // remove the data property + var typedClass = canBeResolved + .WithTypeParameterList(TypeParameterList(SingletonSeparatedList(TypeParameter("T")))) + .WithMembers(canBeResolved.Members.Replace(property, GetPropertyImpl(property, dataInterfaceName).WithType(IdentifierName("T")))) + .AddMembers( + GetWithDataMethod(canBeResolved, TypeConstraint(NullableType(IdentifierName("HandlerIdentity")))), + GetExplicitProperty(property, dataInterfaceName), + GetJDataProperty(), + GetConvertFromOperator(canBeResolved, dataInterfaceName), + GetConvertToOperator(canBeResolved, dataInterfaceName) + ) + .WithAttributeLists( + List( + canBeResolved.AttributeLists + .Where(z => !z.ToFullString().Contains("Method") && !z.ToFullString().Contains("GenerateTypedData")) + ) + ) + .WithBaseList(BaseList(SingletonSeparatedList(SimpleBaseType(dataInterfaceName)))) + .WithConstraintClauses( + SingletonList( + TypeParameterConstraintClause(IdentifierName("T")) + .WithConstraints( + SeparatedList( + new SyntaxNodeOrToken[] { + TypeConstraint(NullableType(IdentifierName("HandlerIdentity"))), + Token(SyntaxKind.CommaToken), + ConstructorConstraint() + } + ) + ) + ) + ) + .AddAttributeLists( + AttributeList( + SeparatedList( + new[] { + Attribute(ParseName("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")), + Attribute(ParseName("System.Runtime.CompilerServices.CompilerGeneratedAttribute")) + } + ) + ) + ) + .WithLeadingTrivia(canBeResolved.GetLeadingTrivia().Where(z => !z.ToString().Contains("#nullable"))) + .WithTrailingTrivia(canBeResolved.GetTrailingTrivia().Where(z => !z.ToString().Contains("#nullable"))) + ; + + if (isContainer) + { + var typedContainer = CreateContainerClass(typedClass) + .WithTypeParameterList(TypeParameterList(SingletonSeparatedList(TypeParameter("T")))) + .WithConstraintClauses( + SingletonList( + TypeParameterConstraintClause(IdentifierName("T")) + .WithConstraints( + SeparatedList( + new TypeParameterConstraintSyntax[] { + TypeConstraint(NullableType(IdentifierName("HandlerIdentity"))), + ConstructorConstraint() + } + ) + ) + ) + ); + + var typedArgumentList = TypeArgumentList(SingletonSeparatedList(IdentifierName("T"))); + typedContainer = typedContainer + .AddMembers( + ConversionOperatorDeclaration(Token(SyntaxKind.ImplicitKeyword), IdentifierName(typedContainer.Identifier)) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword))) + .WithParameterList( + ParameterList( + SingletonSeparatedList( + Parameter(Identifier("container")) + .WithType(GenericName(typedContainer.Identifier).WithTypeArgumentList(typedArgumentList)) + ) + ) + ) + .WithExpressionBody( + ArrowExpressionClause( + ObjectCreationExpression(IdentifierName(typedContainer.Identifier)) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("container"), + IdentifierName("Select") + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + SimpleLambdaExpression(Parameter(Identifier("z"))) + .WithExpressionBody( + CastExpression( + IdentifierName(canBeResolved.Identifier), + IdentifierName("z") + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + .WithSemicolonToken( + Token(SyntaxKind.SemicolonToken) + ) + ); + + compilationMembers.Add(typedContainer); + } + + var cu = CompilationUnit() + .WithUsings(canBeResolved.SyntaxTree.GetCompilationUnitRoot().Usings) + .AddMembers( + NamespaceDeclaration(ParseName(typeSymbol.ContainingNamespace.ToDisplayString())) + .AddMembers(partialClass, typedClass) + .AddMembers(compilationMembers.ToArray()) + .WithLeadingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))) + .WithTrailingTrivia(TriviaList(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))) + ) + .WithLeadingTrivia(Comment(Preamble.GeneratedByATool)) + .WithTrailingTrivia(CarriageReturnLineFeed); + foreach (var ns in RequiredUsings) + { + if (cu.Usings.All(z => z.Name.ToFullString() != ns)) + { + cu = cu.AddUsings(UsingDirective(ParseName(ns))); + } + } + + context.AddSource( + $"{canBeResolved.Identifier.Text}Typed.cs", + cu.NormalizeWhitespace().SyntaxTree.GetRoot().GetText(Encoding.UTF8) + ); + } + } + + private static MethodDeclarationSyntax GetWithDataMethod(ClassDeclarationSyntax syntax, TypeParameterConstraintSyntax constraintSyntax) + { + return MethodDeclaration( + GenericName(Identifier(syntax.Identifier.Text)) + .WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList(IdentifierName("TData")))), + Identifier("WithData") + ) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) + .WithTypeParameterList(TypeParameterList(SingletonSeparatedList(TypeParameter(Identifier("TData"))))) + .WithParameterList(ParameterList(SingletonSeparatedList(Parameter(Identifier("data")).WithType(IdentifierName("TData"))))) + .WithConstraintClauses( + SingletonList( + TypeParameterConstraintClause(IdentifierName("TData")) + .WithConstraints(SeparatedList(new[] { constraintSyntax, ConstructorConstraint() })) + ) + ) + .WithBody( + Block( + SingletonList( + ReturnStatement( + ObjectCreationExpression(GenericName(Identifier(syntax.Identifier.Text)) + .WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList(IdentifierName("TData")))) + ) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + GetMapping(syntax, null).Concat( + new ExpressionSyntax[] { + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName("Data"), + IdentifierName("data") + ) + } + ) + ) + ) + ) + ) + ) + ) + ); + } + + private static IEnumerable GetMapping(ClassDeclarationSyntax syntax, IdentifierNameSyntax? paramName) + { + return syntax.Members.OfType() + .Where(z => z.AccessorList?.Accessors.Any(a => a.Keyword.Kind() == SyntaxKind.SetKeyword || a.Keyword.Kind() == SyntaxKind.InitKeyword) == true) + .Where(z => z.Identifier.Text != "Data") + .Select( + property => AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName(property.Identifier.Text), + paramName == null + ? IdentifierName(property.Identifier.Text) + : MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + paramName, + IdentifierName(property.Identifier.Text) + ) + ) + ); + } + + private static ConversionOperatorDeclarationSyntax GetConvertToOperator(ClassDeclarationSyntax syntax, IdentifierNameSyntax dataInterfaceName) + { + var name = IdentifierName(syntax.Identifier.Text); + var identifier = Identifier(syntax.Identifier.Text); + var paramName = IdentifierName("value"); + return ConversionOperatorDeclaration(Token(SyntaxKind.ImplicitKeyword), name) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword))) + .WithParameterList( + ParameterList( + SingletonSeparatedList( + Parameter( + Identifier("value") + ) + .WithType( + GenericName(identifier) + .WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList(IdentifierName("T")))) + ) + ) + ) + ) + .WithExpressionBody( + ArrowExpressionClause( + ObjectCreationExpression(name) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + GetMapping(syntax, paramName) + .Concat( + new[] { + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName("Data"), + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParenthesizedExpression(CastExpression(dataInterfaceName, paramName)), + IdentifierName("Data") + ) + ) + } + ) + ) + ) + ) + ) + ) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + } + + private static ConversionOperatorDeclarationSyntax GetConvertFromOperator(ClassDeclarationSyntax syntax, IdentifierNameSyntax dataInterfaceName) + { + var name = IdentifierName(syntax.Identifier.Text); + var identifier = Identifier(syntax.Identifier.Text); + var paramName = IdentifierName("value"); + var paramIdentifier = Identifier("value"); + return ConversionOperatorDeclaration( + Token(SyntaxKind.ImplicitKeyword), + GenericName(identifier).WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList(IdentifierName("T")))) + ) + .WithModifiers(TokenList(new[] { Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword) })) + .WithParameterList( + ParameterList( + SingletonSeparatedList(Parameter(paramIdentifier).WithType(name)) + ) + ) + .WithExpressionBody( + ArrowExpressionClause( + ObjectCreationExpression( + GenericName(identifier) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + IdentifierName("T") + ) + ) + ) + ) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + GetMapping(syntax, paramName) + .Concat( + new[] { + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName("JData"), + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParenthesizedExpression(CastExpression(dataInterfaceName, paramName)), + IdentifierName("Data") + ) + ) + } + ) + ) + ) + ) + ) + ) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + } + + private static PropertyDeclarationSyntax GetExplicitProperty(PropertyDeclarationSyntax syntax, IdentifierNameSyntax dataInterfaceName) + { + return syntax + .WithExplicitInterfaceSpecifier(ExplicitInterfaceSpecifier(dataInterfaceName)) + .WithModifiers(TokenList()) + .WithAttributeLists(List(SeparatedList())) + .WithAccessorList( + AccessorList( + List( + new[] { + AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), + AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + } + ) + ) + ); + } + + private static PropertyDeclarationSyntax GetJDataProperty() + { + return PropertyDeclaration(NullableType(IdentifierName("JToken")), Identifier("JData")) + .WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword))) + .WithAccessorList( + AccessorList( + List( + new[] { + AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithExpressionBody( + ArrowExpressionClause( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParenthesizedExpression( + CastExpression( + IdentifierName("ICanBeResolved"), + ThisExpression() + ) + ), + IdentifierName("Data") + ) + ) + ) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), + AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithExpressionBody( + ArrowExpressionClause( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParenthesizedExpression( + CastExpression( + IdentifierName("ICanBeResolved"), + ThisExpression() + ) + ), + IdentifierName("Data") + ), + IdentifierName("value") + ) + ) + ) + .WithSemicolonToken( + Token(SyntaxKind.SemicolonToken) + ) + } + ) + ) + ); + } + + static PropertyDeclarationSyntax GetPropertyImpl(PropertyDeclarationSyntax syntax, IdentifierNameSyntax dataInterfaceName) + { + var canBeResolvedAccess = MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParenthesizedExpression(CastExpression(dataInterfaceName, ThisExpression())), + IdentifierName("Data") + ); + return syntax.WithAccessorList( + AccessorList( + List( + new[] { + AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithExpressionBody( + ArrowExpressionClause( + PostfixUnaryExpression( + SyntaxKind.SuppressNullableWarningExpression, + ConditionalAccessExpression( + canBeResolvedAccess, + InvocationExpression( + MemberBindingExpression( + GenericName(Identifier("ToObject")) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList(IdentifierName("T")) + ) + ) + ) + ) + ) + ) + ) + ).WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), + AccessorDeclaration( + SyntaxKind.SetAccessorDeclaration + ) + .WithExpressionBody( + ArrowExpressionClause( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + canBeResolvedAccess, + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("JToken"), + IdentifierName("FromObject") + ) + ) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("value") + ) + ) + ) + ) + ) + ) + ).WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + } + ) + ) + ); + } + + private static ClassDeclarationSyntax CreateContainerClass(ClassDeclarationSyntax syntax) + { + TypeSyntax typeName = IdentifierName(syntax.Identifier.Text); + var classIdentifier = Identifier($"{syntax.Identifier.Text}Container"); + TypeSyntax className = IdentifierName($"{syntax.Identifier.Text}Container"); + if (syntax.Arity > 0) + { + typeName = GenericName(syntax.Identifier.Text) + .WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList(IdentifierName("T")))); + className = GenericName($"{syntax.Identifier.Text}Container") + .WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList(IdentifierName("T")))); + } + + var typeArgumentList = TypeArgumentList(SingletonSeparatedList(typeName)); + + return ClassDeclaration(classIdentifier) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.PartialKeyword))) + .WithBaseList( + BaseList( + SingletonSeparatedList( + SimpleBaseType( + GenericName(Identifier("ContainerBase")) + .WithTypeArgumentList(typeArgumentList) + ) + ) + ) + ) + .WithMembers( + List( + new MemberDeclarationSyntax[] { + ConstructorDeclaration(classIdentifier) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword) + ) + ) + .WithInitializer( + ConstructorInitializer( + SyntaxKind.ThisConstructorInitializer, + ArgumentList( + SingletonSeparatedList( + Argument( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("Enumerable"), + GenericName(Identifier("Empty")) + .WithTypeArgumentList(typeArgumentList) + ) + ) + ) + ) + ) + ) + ) + .WithBody( + Block() + ), + ConstructorDeclaration(classIdentifier) + .WithModifiers( + TokenList( + Token(SyntaxKind.PublicKeyword) + ) + ) + .WithParameterList( + ParameterList( + SingletonSeparatedList( + Parameter(Identifier("items")).WithType(GenericName(Identifier("IEnumerable")).WithTypeArgumentList(typeArgumentList)) + ) + ) + ) + .WithInitializer( + ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, ArgumentList(SingletonSeparatedList(Argument(IdentifierName("items"))))) + ) + .WithBody(Block()), + ConstructorDeclaration(classIdentifier) + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) + .WithParameterList(ParameterList(SingletonSeparatedList(ArrayParameter(typeName).WithModifiers(TokenList(Token(SyntaxKind.ParamsKeyword)))))) + .WithInitializer( + ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, ArgumentList(SingletonSeparatedList(Argument(IdentifierName("items"))))) + ) + .WithBody(Block()), + AddConversionBody( + typeName, + Identifier("List"), + ConversionOperatorDeclaration( + Token(SyntaxKind.ImplicitKeyword), + className + ) + ) + // array init is different param list + .WithParameterList(ArrayParameters(typeName)), + AddConversionBody( + typeName, + Identifier("List"), + MethodDeclaration(className, Identifier("Create")) + ) + .WithParameterList(ParameterList(SingletonSeparatedList(ArrayParameter(typeName).WithModifiers(TokenList(Token(SyntaxKind.ParamsKeyword)))))), + AddConversionBody( + typeName, + Identifier("Collection"), + ConversionOperatorDeclaration( + Token(SyntaxKind.ImplicitKeyword), + className + ) + ), + AddConversionBody( + typeName, + Identifier("Collection"), + MethodDeclaration( + className, + Identifier("Create") + ) + ), + AddConversionBody( + typeName, + Identifier("List"), + ConversionOperatorDeclaration( + Token(SyntaxKind.ImplicitKeyword), + className + ) + ), + + AddConversionBody( + typeName, + Identifier("List"), MethodDeclaration( + className, + Identifier("Create") + ) + ), + AddConversionBody( + typeName, + Identifier("List"), + ConversionOperatorDeclaration( + Token(SyntaxKind.ImplicitKeyword), + className + ) + ) + .WithParameterList(ImmutableArrayParameters(typeName)), + + AddConversionBody( + typeName, + Identifier("List"), + MethodDeclaration( + className, + Identifier("Create") + ) + ) + .WithParameterList(ImmutableArrayParameters(typeName)), + + AddConversionBody( + typeName, + Identifier("ImmutableList"), + ConversionOperatorDeclaration( + Token(SyntaxKind.ImplicitKeyword), + className + ) + ), + + AddConversionBody( + typeName, + Identifier("ImmutableList"), MethodDeclaration( + className, + Identifier("Create") + ) + ) + } + ) + ); + + static ParameterListSyntax ImmutableArrayParameters(TypeSyntax typeName) + { + return ParameterList( + SingletonSeparatedList( + Parameter( + Identifier("items") + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.InKeyword) + ) + ) + .WithType( + GenericName( + Identifier("ImmutableArray") + ) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + typeName + ) + ) + ) + ) + ) + ); + } + + static ParameterListSyntax ArrayParameters(TypeSyntax typeName) + { + return ParameterList(SingletonSeparatedList(ArrayParameter(typeName))); + } + + static ParameterSyntax ArrayParameter(TypeSyntax typeName) + { + return Parameter(Identifier("items")) + .WithType( + ArrayType(typeName) + .WithRankSpecifiers( + SingletonList( + ArrayRankSpecifier( + SingletonSeparatedList( + OmittedArraySizeExpression() + ) + ) + ) + ) + ); + } + + static BaseMethodDeclarationSyntax AddConversionBody(TypeSyntax typeName, SyntaxToken collectionName, BaseMethodDeclarationSyntax syntax) + { + return syntax + .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword))) + .WithParameterList( + ParameterList( + SingletonSeparatedList( + Parameter( + Identifier("items") + ) + .WithType( + GenericName(collectionName) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList( + typeName + ) + ) + ) + ) + ) + ) + ) + .WithExpressionBody( + ArrowExpressionClause( + ObjectCreationExpression(syntax is ConversionOperatorDeclarationSyntax d ? d.Type : syntax is MethodDeclarationSyntax m ? m.ReturnType : null!) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + IdentifierName("items") + ) + ) + ) + ) + ) + ) + .WithSemicolonToken( + Token(SyntaxKind.SemicolonToken) + ); + } + } + + /// + /// Created on demand before each generation pass + /// + internal class SyntaxReceiver : ISyntaxReceiver + { + public List CanBeResolved { get; } = new(); + public List CanHaveData { get; } = new(); + public List CreateContainers { get; } = new(); + + /// + /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + /// + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + // any field with at least one attribute is a candidate for property generation + if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax) + { + if (classDeclarationSyntax.AttributeLists + .SelectMany(z => z.Attributes) + .Any(z => z.Name.ToFullString().Contains("GenerateContainer")) + ) + { + CreateContainers.Add(classDeclarationSyntax); + } + + if ( + classDeclarationSyntax.BaseList != null && + classDeclarationSyntax.SyntaxTree.HasCompilationUnitRoot && + classDeclarationSyntax.Members.OfType().Any(z => z.Identifier.Text == "Data") + ) + { + if (classDeclarationSyntax.BaseList.Types.Any(z => z.ToString().EndsWith("ICanBeResolved"))) + { + CanBeResolved.Add(classDeclarationSyntax); + } + + if (classDeclarationSyntax.BaseList.Types.Any(z => z.ToString().EndsWith("ICanHaveData"))) + { + CanHaveData.Add(classDeclarationSyntax); + } + } + } + } + } + } +} diff --git a/src/JsonRpc/Generation/GenerateHandlerMethodsAttribute.cs b/src/JsonRpc/Generation/GenerateHandlerMethodsAttribute.cs index fead28ca1..dee2fde23 100644 --- a/src/JsonRpc/Generation/GenerateHandlerMethodsAttribute.cs +++ b/src/JsonRpc/Generation/GenerateHandlerMethodsAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using CodeGeneration.Roslyn; namespace OmniSharp.Extensions.JsonRpc.Generation { @@ -10,8 +9,7 @@ namespace OmniSharp.Extensions.JsonRpc.Generation /// /// Efforts will be made to make this available for consumers once source generators land /// - [AttributeUsage(AttributeTargets.Interface)] - [CodeGenerationAttribute("OmniSharp.Extensions.JsonRpc.Generators.GenerateHandlerMethodsGenerator, OmniSharp.Extensions.JsonRpc.Generators")] + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)] [Conditional("CodeGeneration")] public class GenerateHandlerMethodsAttribute : Attribute { diff --git a/src/JsonRpc/Generation/GenerateRequestMethodsAttribute.cs b/src/JsonRpc/Generation/GenerateRequestMethodsAttribute.cs index 37bb2766f..bf04425a3 100644 --- a/src/JsonRpc/Generation/GenerateRequestMethodsAttribute.cs +++ b/src/JsonRpc/Generation/GenerateRequestMethodsAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using CodeGeneration.Roslyn; namespace OmniSharp.Extensions.JsonRpc.Generation { @@ -10,8 +9,7 @@ namespace OmniSharp.Extensions.JsonRpc.Generation /// /// Efforts will be made to make this available for consumers once source generators land /// - [AttributeUsage(AttributeTargets.Interface)] - [CodeGenerationAttribute("OmniSharp.Extensions.JsonRpc.Generators.GenerateRequestMethodsGenerator, OmniSharp.Extensions.JsonRpc.Generators")] + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)] [Conditional("CodeGeneration")] public class GenerateRequestMethodsAttribute : Attribute { diff --git a/src/JsonRpc/JsonRpc.csproj b/src/JsonRpc/JsonRpc.csproj index d349f23b6..712b5cb96 100644 --- a/src/JsonRpc/JsonRpc.csproj +++ b/src/JsonRpc/JsonRpc.csproj @@ -15,8 +15,8 @@ - + diff --git a/src/JsonRpc/Nullable/Records.cs b/src/JsonRpc/Nullable/Records.cs new file mode 100644 index 000000000..66cef4915 --- /dev/null +++ b/src/JsonRpc/Nullable/Records.cs @@ -0,0 +1,23 @@ +#pragma warning disable MA0048 // File name must match type name +#define INTERNAL_RECORD_ATTRIBUTES +#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 +using System.ComponentModel; + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] +#if INTERNAL_RECORD_ATTRIBUTES + internal +#else + public +#endif + static class IsExternalInit + { + } +} +#endif diff --git a/src/Protocol/Attributes.cs b/src/Protocol/Attributes.cs new file mode 100644 index 000000000..e324f76d0 --- /dev/null +++ b/src/Protocol/Attributes.cs @@ -0,0 +1,71 @@ +using System; +using System.Diagnostics; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals; + +namespace OmniSharp.Extensions.LanguageServer.Protocol +{ + /// + /// Allows generating a typed counterpart to any model that implements + /// + /// + /// Efforts will be made to make this available for consumers once source generators land + /// + [AttributeUsage(AttributeTargets.Class)] + [Conditional("CodeGeneration")] + public class GenerateTypedDataAttribute : Attribute { } + + /// + /// Allows generating a typed container counterpart to any model that implements + /// + /// + /// Efforts will be made to make this available for consumers once source generators land + /// + [AttributeUsage(AttributeTargets.Class)] + [Conditional("CodeGeneration")] + public class GenerateContainerAttribute : Attribute { } + + /// + /// Generates work done on a registration options object + /// + [AttributeUsage(AttributeTargets.Class)] + [Conditional("CodeGeneration")] + public class WorkDoneProgressAttribute : Attribute { } + + /// + /// Generates text document on a registration options object + /// + [AttributeUsage(AttributeTargets.Class)] + [Conditional("CodeGeneration")] + public class TextDocumentAttribute : Attribute { } + + /// + /// Defines a converter that is used for converting from dynamic to static + /// + [AttributeUsage(AttributeTargets.Class)] +// [Conditional("CodeGeneration")] + public class RegistrationOptionsAttribute : Attribute + { + public string ServerCapabilitiesKey { get; } + + public RegistrationOptionsAttribute(string serverCapabilitiesKey) + { + ServerCapabilitiesKey = serverCapabilitiesKey; + } + } + + /// + /// Defines a converter that is used for converting from dynamic to static + /// + [AttributeUsage(AttributeTargets.Class)] +// [Conditional("CodeGeneration")] + public class RegistrationOptionsConverterAttribute : Attribute + { + public Type ConverterType { get; } + + public RegistrationOptionsConverterAttribute(Type converterType) + { + ConverterType = converterType; + } + } +} diff --git a/src/Protocol/Protocol.csproj b/src/Protocol/Protocol.csproj index 980b11d8a..095658316 100644 --- a/src/Protocol/Protocol.csproj +++ b/src/Protocol/Protocol.csproj @@ -7,6 +7,11 @@ Language Server Protocol models, classes, interfaces and helper methods + + true + $(BaseIntermediateOutputPath)\GeneratedFiles + + diff --git a/test/Generation.Tests/GenerationHelpers.cs b/test/Generation.Tests/GenerationHelpers.cs index de4a19f8e..834a3072c 100644 --- a/test/Generation.Tests/GenerationHelpers.cs +++ b/test/Generation.Tests/GenerationHelpers.cs @@ -1,19 +1,25 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using CodeGeneration.Roslyn; -using CodeGeneration.Roslyn.Engine; using FluentAssertions; using MediatR; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; +using Newtonsoft.Json.Linq; +using NSubstitute; using OmniSharp.Extensions.DebugAdapter.Protocol.Client; using OmniSharp.Extensions.JsonRpc.Generation; +using OmniSharp.Extensions.JsonRpc.Generators; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using Xunit; @@ -39,10 +45,10 @@ static GenerationHelpers() coreAssemblyNames.Select(x => MetadataReference.CreateFromFile(Path.Combine(coreAssemblyPath, x))); var otherAssemblies = new[] { typeof(CSharpCompilation).Assembly, - typeof(CodeGenerationAttributeAttribute).Assembly, typeof(GenerateHandlerMethodsAttribute).Assembly, typeof(IDebugAdapterClientRegistry).Assembly, typeof(Unit).Assembly, + typeof(JToken).Assembly, typeof(ILanguageServerRegistry).Assembly, }; MetadataReferences = coreMetaReferences @@ -56,23 +62,24 @@ static GenerationHelpers() internal const string CSharpDefaultFileExt = "cs"; internal const string TestProjectName = "TestProject"; - internal static readonly string NormalizedPreamble = NormalizeToLf(DocumentTransform.GeneratedByAToolPreamble + Lf); + internal static readonly string NormalizedPreamble = NormalizeToLf(Preamble.GeneratedByATool + Lf); internal static readonly ImmutableArray MetadataReferences; - public static async Task AssertGeneratedAsExpected(string source, string expected) + public static async Task AssertGeneratedAsExpected(string source, string expected) where T : ISourceGenerator, new() { - var generatedTree = await GenerateAsync(source); + var generatedTree = await GenerateAsync(source); // normalize line endings to just LF - var generatedText = NormalizeToLf(generatedTree.GetText().ToString()); + var generatedText = NormalizeToLf(generatedTree.GetText().ToString()).Trim(); // and append preamble to the expected var expectedText = NormalizedPreamble + NormalizeToLf(expected).Trim(); +// Assert.Equal(expectedText, generatedText); generatedText.Should().Be(expectedText); } - public static async Task Generate(string source) + public static async Task Generate(string source) where T : ISourceGenerator, new() { - var generatedTree = await GenerateAsync(source); + var generatedTree = await GenerateAsync(source); // normalize line endings to just LF var generatedText = NormalizeToLf(generatedTree.GetText().ToString()); // and append preamble to the expected @@ -81,7 +88,7 @@ public static async Task Generate(string source) public static string NormalizeToLf(string input) => input.Replace(CrLf, Lf); - public static async Task GenerateAsync(string source) + public static async Task GenerateAsync(string source) where T : ISourceGenerator, new() { var document = CreateProject(source).Documents.Single(); var tree = await document.GetSyntaxTreeAsync(); @@ -97,10 +104,21 @@ public static async Task GenerateAsync(string source) } var diagnostics = compilation.GetDiagnostics(); +// Assert.Empty(diagnostics.Where(x => x.Severity >= DiagnosticSeverity.Warning)); + + ISourceGenerator generator = new T(); + + var driver = CSharpGeneratorDriver.Create( + ImmutableArray.Create(generator), + ImmutableArray.Empty, + compilation.SyntaxTrees[0].Options as CSharpParseOptions + ); + + driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out diagnostics); Assert.Empty(diagnostics.Where(x => x.Severity >= DiagnosticSeverity.Warning)); - var progress = new Progress(); - var result = await DocumentTransform.TransformAsync(compilation, tree, null, Assembly.Load, progress, CancellationToken.None); - return result; + + // the syntax tree added by the generator will be the last one in the compilation + return outputCompilation.SyntaxTrees.Last(); } public static Project CreateProject(params string[] sources) @@ -137,4 +155,21 @@ public static Project CreateProject(params string[] sources) return project; } } + + class NotSureWhatToCallYou : CSharpSyntaxWalker + { + private readonly ISyntaxReceiver _syntaxReceiver; + + public NotSureWhatToCallYou(ISyntaxReceiver syntaxReceiver) + { + _syntaxReceiver = syntaxReceiver; + } + + public override void Visit(SyntaxNode? node) + { + if (node == null) return; + _syntaxReceiver.OnVisitSyntaxNode(node); + base.Visit(node); + } + } } diff --git a/test/Generation.Tests/JsonRpcGenerationTests.cs b/test/Generation.Tests/JsonRpcGenerationTests.cs index a57ee19d9..44d036d16 100644 --- a/test/Generation.Tests/JsonRpcGenerationTests.cs +++ b/test/Generation.Tests/JsonRpcGenerationTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using OmniSharp.Extensions.JsonRpc.Generators; using TestingUtils; using Xunit; using static Generation.Tests.GenerationHelpers; @@ -30,18 +31,19 @@ public interface IExitHandler : IJsonRpcNotificationHandler } }"; - var expected = @"using System; -using System.Threading; -using System.Threading.Tasks; + var expected = @" using MediatR; +using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Generation; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; namespace OmniSharp.Extensions.LanguageServer.Protocol.Test { @@ -53,20 +55,11 @@ public static partial class ExitExtensions public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Func handler) => registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Action handler) => registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Func handler) => registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); - } -#nullable restore -} - -namespace OmniSharp.Extensions.LanguageServer.Protocol.Test -{ -#nullable enable - public static partial class ExitExtensions - { public static void SendExit(this ILanguageClient mediator, ExitParams @params) => mediator.SendNotification(@params); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } [FactWithSkipOn(SkipOnPlatform.Windows)] @@ -87,17 +80,17 @@ public interface ICapabilitiesHandler : IJsonRpcNotificationHandler handler) => registry.AddHandler(EventNames.Capabilities, NotificationHandler.For(handler)); public static IDebugAdapterClientRegistry OnCapabilities(this IDebugAdapterClientRegistry registry, Action handler) => registry.AddHandler(EventNames.Capabilities, NotificationHandler.For(handler)); public static IDebugAdapterClientRegistry OnCapabilities(this IDebugAdapterClientRegistry registry, Func handler) => registry.AddHandler(EventNames.Capabilities, NotificationHandler.For(handler)); - } -#nullable restore -} - -namespace OmniSharp.Extensions.DebugAdapter.Protocol.Events.Test -{ -#nullable enable - public static partial class CapabilitiesExtensions - { public static void SendCapabilities(this IDebugAdapterServer mediator, CapabilitiesEvent @params) => mediator.SendNotification(@params); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } @@ -149,18 +133,19 @@ public interface IExitHandler : IJsonRpcNotificationHandler } }"; - var expected = @"using System; -using System.Threading; -using System.Threading.Tasks; + var expected = @" using MediatR; +using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Generation; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; namespace Test { @@ -172,20 +157,11 @@ public static partial class ExitExtensions public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Func handler) => registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Action handler) => registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); public static ILanguageServerRegistry OnExit(this ILanguageServerRegistry registry, Func handler) => registry.AddHandler(GeneralNames.Exit, NotificationHandler.For(handler)); - } -#nullable restore -} - -namespace Test -{ -#nullable enable - public static partial class ExitExtensions - { public static void SendExit(this ILanguageClient mediator, ExitParams @params) => mediator.SendNotification(@params); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } [FactWithSkipOn(SkipOnPlatform.Windows)] @@ -213,10 +189,8 @@ public interface IDidChangeTextDocumentHandler : IJsonRpcNotificationHandler(handler, registrationOptions)); } - } -#nullable restore -} -namespace OmniSharp.Extensions.LanguageServer.Protocol.Test -{ -#nullable enable - public static partial class DidChangeTextDocumentExtensions - { public static void DidChangeTextDocument(this ILanguageClient mediator, DidChangeTextDocumentParams @params) => mediator.SendNotification(@params); } #nullable restore -}"; - await AssertGeneratedAsExpected(source, expected); +} +"; + await AssertGeneratedAsExpected(source, expected); } [FactWithSkipOn(SkipOnPlatform.Windows)] @@ -311,20 +280,20 @@ public interface IFoldingRangeHandler : IJsonRpcRequestHandler, FoldingRangeCapability, FoldingRangeRegistrationOptions>(handler, registrationOptions)); } - } -#nullable restore -} -namespace OmniSharp.Extensions.LanguageServer.Protocol.Test -{ -#nullable enable - public static partial class FoldingRangeExtensions - { public static IRequestProgressObservable, Container> RequestFoldingRange(this ITextDocumentLanguageClient mediator, FoldingRangeRequestParam @params, CancellationToken cancellationToken = default) => mediator.ProgressManager.MonitorUntil(@params, value => new Container(value), cancellationToken); public static IRequestProgressObservable, Container> RequestFoldingRange(this ILanguageClient mediator, FoldingRangeRequestParam @params, CancellationToken cancellationToken = default) => mediator.ProgressManager.MonitorUntil(@params, value => new Container(value), cancellationToken); } #nullable restore -}"; - await AssertGeneratedAsExpected(source, expected); +} +"; + await AssertGeneratedAsExpected(source, expected); } [FactWithSkipOn(SkipOnPlatform.Windows)] @@ -407,20 +369,20 @@ public interface IDefinitionHandler : IJsonRpcRequestHandler(handler, registrationOptions)); } - } -#nullable restore -} -namespace OmniSharp.Extensions.LanguageServer.Protocol.Test -{ -#nullable enable - public static partial class DefinitionExtensions - { public static IRequestProgressObservable, LocationOrLocationLinks> RequestDefinition(this ILanguageClient mediator, DefinitionParams @params, CancellationToken cancellationToken = default) => mediator.ProgressManager.MonitorUntil(@params, value => new LocationOrLocationLinks(value), cancellationToken); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } @@ -503,20 +457,20 @@ public interface IDefinitionHandler : IJsonRpcRequestHandler(handler, registrationOptions)); } - } -#nullable restore -} -namespace Test -{ -#nullable enable - public static partial class DefinitionExtensions - { public static IRequestProgressObservable, LocationOrLocationLinks> RequestDefinition(this ITextDocumentLanguageClient mediator, DefinitionParams @params, CancellationToken cancellationToken = default) => mediator.ProgressManager.MonitorUntil(@params, value => new LocationOrLocationLinks(value), cancellationToken); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } [FactWithSkipOn(SkipOnPlatform.Windows)] @@ -595,18 +541,18 @@ namespace Test public interface ILanguageProtocolInitializeHandler : IJsonRpcRequestHandler {} }"; var expected = @" -using System; -using System.Threading; -using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Generation; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using System; using System.Collections.Generic; -using MediatR; -using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; namespace Test { @@ -616,20 +562,11 @@ public static partial class LanguageProtocolInitializeExtensions { public static ILanguageServerRegistry OnLanguageProtocolInitialize(this ILanguageServerRegistry registry, Func> handler) => registry.AddHandler(GeneralNames.Initialize, RequestHandler.For(handler)); public static ILanguageServerRegistry OnLanguageProtocolInitialize(this ILanguageServerRegistry registry, Func> handler) => registry.AddHandler(GeneralNames.Initialize, RequestHandler.For(handler)); - } -#nullable restore -} - -namespace Test -{ -#nullable enable - public static partial class LanguageProtocolInitializeExtensions - { public static Task RequestLanguageProtocolInitialize(this ITextDocumentLanguageClient mediator, InitializeParams @params, CancellationToken cancellationToken = default) => mediator.SendRequest(@params, cancellationToken); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } [FactWithSkipOn(SkipOnPlatform.Windows)] @@ -651,17 +588,17 @@ namespace OmniSharp.Extensions.DebugAdapter.Protocol.Requests public interface IAttachHandler : IJsonRpcRequestHandler { } }"; var expected = @" -using System; -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.JsonRpc.Generation; -using System.Collections.Generic; using MediatR; using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.DebugAdapter.Protocol; using OmniSharp.Extensions.DebugAdapter.Protocol.Client; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace OmniSharp.Extensions.DebugAdapter.Protocol.Requests { @@ -675,20 +612,11 @@ public static IDebugAdapterServerRegistry OnAttach(this IDebugAdapterServerRe where T : AttachRequestArguments => registry.AddHandler(RequestNames.Attach, RequestHandler.For(handler)); public static IDebugAdapterServerRegistry OnAttach(this IDebugAdapterServerRegistry registry, Func> handler) where T : AttachRequestArguments => registry.AddHandler(RequestNames.Attach, RequestHandler.For(handler)); - } -#nullable restore -} - -namespace OmniSharp.Extensions.DebugAdapter.Protocol.Requests -{ -#nullable enable - public static partial class AttachExtensions - { public static Task RequestAttach(this IDebugAdapterClient mediator, AttachRequestArguments @params, CancellationToken cancellationToken = default) => mediator.SendRequest(@params, cancellationToken); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } [Fact] @@ -712,17 +640,17 @@ public interface IAttachHandler : IJsonRpcRequestHandler(this IDebugAdapterServerRe where T : AttachRequestArguments => registry.AddHandler(RequestNames.Attach, RequestHandler.For(handler)); public static IDebugAdapterServerRegistry OnAttach(this IDebugAdapterServerRegistry registry, Func> handler) where T : AttachRequestArguments => registry.AddHandler(RequestNames.Attach, RequestHandler.For(handler)); - } -#nullable restore -} - -namespace OmniSharp.Extensions.DebugAdapter.Protocol.Requests -{ -#nullable enable - public static partial class AttachExtensions - { public static Task RequestAttach(this IDebugAdapterClient mediator, AttachRequestArguments @params, CancellationToken cancellationToken = default) => mediator.SendRequest(@params, cancellationToken); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } [FactWithSkipOn(SkipOnPlatform.Windows)] @@ -776,17 +695,17 @@ public interface IAttachHandler : IJsonRpcRequestHandler { } }"; var expected = @" -using System; -using System.Threading; -using System.Threading.Tasks; -using OmniSharp.Extensions.JsonRpc; -using OmniSharp.Extensions.JsonRpc.Generation; -using System.Collections.Generic; using MediatR; using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.DebugAdapter.Protocol; using OmniSharp.Extensions.DebugAdapter.Protocol.Client; using OmniSharp.Extensions.DebugAdapter.Protocol.Server; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace OmniSharp.Extensions.DebugAdapter.Protocol.Bogus { @@ -800,20 +719,11 @@ public static IDebugAdapterServerRegistry OnAttach(this IDebugAdapterServerRe where T : AttachRequestArguments => registry.AddHandler(""attach"", RequestHandler.For(handler)); public static IDebugAdapterServerRegistry OnAttach(this IDebugAdapterServerRegistry registry, Func> handler) where T : AttachRequestArguments => registry.AddHandler(""attach"", RequestHandler.For(handler)); - } -#nullable restore -} - -namespace OmniSharp.Extensions.DebugAdapter.Protocol.Bogus -{ -#nullable enable - public static partial class AttachExtensions - { public static Task RequestAttach(this IDebugAdapterClient mediator, AttachRequestArguments @params, CancellationToken cancellationToken = default) => mediator.SendRequest(@params, cancellationToken); } #nullable restore }"; - await AssertGeneratedAsExpected(source, expected); + await AssertGeneratedAsExpected(source, expected); } } } diff --git a/test/Generation.Tests/TypedCanBeResolvedTests.cs b/test/Generation.Tests/TypedCanBeResolvedTests.cs new file mode 100644 index 000000000..603b4768b --- /dev/null +++ b/test/Generation.Tests/TypedCanBeResolvedTests.cs @@ -0,0 +1,301 @@ +using System.Threading.Tasks; +using OmniSharp.Extensions.JsonRpc.Generators; +using TestingUtils; + +namespace Generation.Tests +{ + public class TypedCanBeResolvedTests + { + [FactWithSkipOn(SkipOnPlatform.Windows)] + public async Task Supports_Generating_Strongly_Typed_ICanBeResolved_Data() + { + var source = @" +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +#nullable enable +namespace OmniSharp.Extensions.LanguageServer.Protocol.Test +{ + /// + /// A code lens represents a command that should be shown along with + /// source text, like the number of references, a way to run tests, etc. + /// + /// A code lens is _unresolved_ when no command is associated to it. For performance + /// reasons the creation of a code lens and resolving should be done in two stages. + /// + [DebuggerDisplay(""{"" + nameof(DebuggerDisplay) + "",nq}"")] + [Method(TextDocumentNames.CodeLensResolve, Direction.ClientToServer)] + [GenerateTypedData, GenerateContainer] + public partial class CodeLens : IRequest, ICanBeResolved + { + /// + /// The range in which this code lens is valid. Should only span a single line. + /// + public Range Range { get; set; } = null!; + [Optional] + public Command? Command { get; set; } + /// + /// A data entry field that is preserved on a code lens item between + /// a code lens and a code lens resolve request. + /// + [Optional] + public JToken? Data { get; set; } + private string DebuggerDisplay => $""{Range}{( Command != null ? $"" {Command}"" : """" )}""; + public override string ToString() => DebuggerDisplay; + } +} +#nullable restore"; + + var expected = @" +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Immutable; +using System.Linq; + +#nullable enable +namespace OmniSharp.Extensions.LanguageServer.Protocol.Test +{ + public partial class CodeLens + { + public CodeLens WithData(TData data) + where TData : HandlerIdentity? , new() + { + return new CodeLens{Range = Range, Command = Command, Data = data}; + } + } + + /// + /// A code lens represents a command that should be shown along with + /// source text, like the number of references, a way to run tests, etc. + /// + /// A code lens is _unresolved_ when no command is associated to it. For performance + /// reasons the creation of a code lens and resolving should be done in two stages. + /// + [DebuggerDisplay(""{"" + nameof(DebuggerDisplay) + "",nq}"")] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute, System.Runtime.CompilerServices.CompilerGeneratedAttribute] + public partial class CodeLens : ICanBeResolved where T : HandlerIdentity? , new() + { + /// + /// The range in which this code lens is valid. Should only span a single line. + /// + public Range Range + { + get; + set; + } + + = null !; + [Optional] + public Command? Command + { + get; + set; + } + + /// + /// A data entry field that is preserved on a code lens item between + /// a code lens and a code lens resolve request. + /// + [Optional] + public T Data + { + get => ((ICanBeResolved)this).Data?.ToObject()!; + set => ((ICanBeResolved)this).Data = JToken.FromObject(value); + } + + private string DebuggerDisplay => $""{Range}{(Command != null ? $"" {Command}"" : """")}""; + public override string ToString() => DebuggerDisplay; + public CodeLens WithData(TData data) + where TData : HandlerIdentity? , new() + { + return new CodeLens{Range = Range, Command = Command, Data = data}; + } + + JToken? ICanBeResolved.Data + { + get; + set; + } + + private JToken? JData + { + get => ((ICanBeResolved)this).Data; + set => ((ICanBeResolved)this).Data = value; + } + + public static implicit operator CodeLens(CodeLens value) => new CodeLens{Range = value.Range, Command = value.Command, JData = ((ICanBeResolved)value).Data}; + public static implicit operator CodeLens(CodeLens value) => new CodeLens{Range = value.Range, Command = value.Command, Data = ((ICanBeResolved)value).Data}; + } + + public partial class CodeLensContainer : ContainerBase> where T : HandlerIdentity? , new() + { + public CodeLensContainer(): this(Enumerable.Empty>()) + { + } + + public CodeLensContainer(IEnumerable> items): base(items) + { + } + + public CodeLensContainer(params CodeLens[] items): base(items) + { + } + + public static implicit operator CodeLensContainer(CodeLens[] items) => new CodeLensContainer(items); + public static CodeLensContainer Create(params CodeLens[] items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(Collection> items) => new CodeLensContainer(items); + public static CodeLensContainer Create(Collection> items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(List> items) => new CodeLensContainer(items); + public static CodeLensContainer Create(List> items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(in ImmutableArray> items) => new CodeLensContainer(items); + public static CodeLensContainer Create(in ImmutableArray> items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(ImmutableList> items) => new CodeLensContainer(items); + public static CodeLensContainer Create(ImmutableList> items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(CodeLensContainer container) => new CodeLensContainer(container.Select(z => (CodeLens)z)); + } +} +#nullable restore + +"; + await GenerationHelpers.AssertGeneratedAsExpected(source, expected); + } + + [FactWithSkipOn(SkipOnPlatform.Windows)] + public async Task Supports_Generating_Strongly_Typed_Container() + { + var source = @" +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +#nullable enable +namespace OmniSharp.Extensions.LanguageServer.Protocol.Test +{ + /// + /// A code lens represents a command that should be shown along with + /// source text, like the number of references, a way to run tests, etc. + /// + /// A code lens is _unresolved_ when no command is associated to it. For performance + /// reasons the creation of a code lens and resolving should be done in two stages. + /// + [DebuggerDisplay(""{"" + nameof(DebuggerDisplay) + "",nq}"")] + [Method(TextDocumentNames.CodeLensResolve, Direction.ClientToServer)] + [GenerateContainer] + public partial class CodeLens : IRequest, ICanBeResolved + { + /// + /// The range in which this code lens is valid. Should only span a single line. + /// + public Range Range { get; set; } = null!; + [Optional] + public Command? Command { get; set; } + /// + /// A data entry field that is preserved on a code lens item between + /// a code lens and a code lens resolve request. + /// + [Optional] + public JToken? Data { get; set; } + private string DebuggerDisplay => $""{Range}{( Command != null ? $"" {Command}"" : """" )}""; + public override string ToString() => DebuggerDisplay; + } +} +#nullable restore"; + + var expected = @" +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Immutable; +using System.Linq; + +#nullable enable +namespace OmniSharp.Extensions.LanguageServer.Protocol.Test +{ + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute, System.Runtime.CompilerServices.CompilerGeneratedAttribute] + public partial class CodeLensContainer : ContainerBase + { + public CodeLensContainer(): this(Enumerable.Empty()) + { + } + + public CodeLensContainer(IEnumerable items): base(items) + { + } + + public CodeLensContainer(params CodeLens[] items): base(items) + { + } + + public static implicit operator CodeLensContainer(CodeLens[] items) => new CodeLensContainer(items); + public static CodeLensContainer Create(params CodeLens[] items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(Collection items) => new CodeLensContainer(items); + public static CodeLensContainer Create(Collection items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(List items) => new CodeLensContainer(items); + public static CodeLensContainer Create(List items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(in ImmutableArray items) => new CodeLensContainer(items); + public static CodeLensContainer Create(in ImmutableArray items) => new CodeLensContainer(items); + public static implicit operator CodeLensContainer(ImmutableList items) => new CodeLensContainer(items); + public static CodeLensContainer Create(ImmutableList items) => new CodeLensContainer(items); + } +} +#nullable restore + +"; + await GenerationHelpers.AssertGeneratedAsExpected(source, expected); + } + } +} diff --git a/test/JsonRpc.Tests/JsonRpc.Tests.csproj b/test/JsonRpc.Tests/JsonRpc.Tests.csproj index 568d9e53b..a85c0bd09 100644 --- a/test/JsonRpc.Tests/JsonRpc.Tests.csproj +++ b/test/JsonRpc.Tests/JsonRpc.Tests.csproj @@ -1,11 +1,11 @@ - netcoreapp3.1;netcoreapp2.1 + netcoreapp3.1;net5.0;netcoreapp2.1 true AnyCPU - + diff --git a/test/Lsp.Tests/Integration/DisableDefaultsTests.cs b/test/Lsp.Tests/Integration/DisableDefaultsTests.cs index d40fdef46..4804420d7 100644 --- a/test/Lsp.Tests/Integration/DisableDefaultsTests.cs +++ b/test/Lsp.Tests/Integration/DisableDefaultsTests.cs @@ -1,10 +1,5 @@ using System; using System.Collections.Generic; -using System.Composition; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -18,10 +13,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; -using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using OmniSharp.Extensions.LanguageServer.Protocol.Window; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; using OmniSharp.Extensions.LanguageServer.Server; using OmniSharp.Extensions.LanguageServer.Shared; diff --git a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs index 3871443a3..17599ca21 100644 --- a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs +++ b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs @@ -356,7 +356,7 @@ public async Task Should_Execute_4_Args() return Task.FromResult( new CompletionList( new CompletionItem { - Command = Command.Create("execute-a", 1, "2", true, new Range(( 0, 1 ), ( 1, 1 ))) + Command = Command.Create("execute-a", 1, "2", true, new Range((0, 1), (1, 1))) } ) ); @@ -368,7 +368,7 @@ public async Task Should_Execute_4_Args() i.Should().Be(1); s.Should().Be("2"); arg3.Should().BeTrue(); - arg4.Should().Be(new Range(( 0, 1 ), ( 1, 1 ))); + arg4.Should().Be(new Range((0, 1), (1, 1))); return Task.CompletedTask; } @@ -397,7 +397,7 @@ public async Task Should_Execute_5_Args() new CompletionList( new CompletionItem { Command = Command.Create( - "execute-a", 1, "2", true, new Range(( 0, 1 ), ( 1, 1 )), new Dictionary { ["a"] = "123", ["b"] = "456" } + "execute-a", 1, "2", true, new Range((0, 1), (1, 1)), new Dictionary { ["a"] = "123", ["b"] = "456" } ) } ) @@ -410,7 +410,7 @@ public async Task Should_Execute_5_Args() i.Should().Be(1); s.Should().Be("2"); arg3.Should().BeTrue(); - arg4.Should().Be(new Range(( 0, 1 ), ( 1, 1 ))); + arg4.Should().Be(new Range((0, 1), (1, 1))); arg5.Should().ContainKeys("a", "b"); return Task.CompletedTask; @@ -440,7 +440,7 @@ public async Task Should_Execute_6_Args() new CompletionList( new CompletionItem { Command = Command.Create( - "execute-a", 1, "2", true, new Range(( 0, 1 ), ( 1, 1 )), new Dictionary { ["a"] = "123", ["b"] = "456" }, + "execute-a", 1, "2", true, new Range((0, 1), (1, 1)), new Dictionary { ["a"] = "123", ["b"] = "456" }, Guid.NewGuid() ) } @@ -454,7 +454,7 @@ public async Task Should_Execute_6_Args() i.Should().Be(1); s.Should().Be("2"); arg3.Should().BeTrue(); - arg4.Should().Be(new Range(( 0, 1 ), ( 1, 1 ))); + arg4.Should().Be(new Range((0, 1), (1, 1))); arg5.Should().ContainKeys("a", "b"); arg6.Should().NotBeEmpty(); diff --git a/test/Lsp.Tests/Lsp.Tests.csproj b/test/Lsp.Tests/Lsp.Tests.csproj index a903ade0f..0d749a9cf 100644 --- a/test/Lsp.Tests/Lsp.Tests.csproj +++ b/test/Lsp.Tests/Lsp.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp3.1;netcoreapp2.1 + netcoreapp3.1;net5.0;netcoreapp2.1 true AnyCPU @@ -9,12 +9,11 @@ - + - diff --git a/test/Lsp.Tests/Models/FormattingOptionsTests.cs b/test/Lsp.Tests/Models/FormattingOptionsTests.cs index 27dec2e08..762e7ee50 100644 --- a/test/Lsp.Tests/Models/FormattingOptionsTests.cs +++ b/test/Lsp.Tests/Models/FormattingOptionsTests.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using Microsoft.CodeAnalysis.CSharp.Syntax; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;