From f752282f3dc4b9dd3d117844e57aa5f557cc423e Mon Sep 17 00:00:00 2001 From: David Barbet Date: Tue, 11 Jun 2024 12:32:18 -0700 Subject: [PATCH] Expose Roslyn LSP types to XAML and add readme --- ...odeAnalysis.LanguageServer.Protocol.csproj | 3 + .../Protocol/LanguageServer.Protocol.csproj | 59 ---------- .../Protocol/LanguageServer.Protocol.ruleset | 44 -------- .../Protocol/Protocol/README.md | 103 ++++++++++++++++++ 4 files changed, 106 insertions(+), 103 deletions(-) delete mode 100644 src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.csproj delete mode 100644 src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.ruleset create mode 100644 src/LanguageServer/Protocol/Protocol/README.md diff --git a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index 218bbf971db4c..b3bbf6f6bc925 100644 --- a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -40,6 +40,9 @@ + + + diff --git a/src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.csproj b/src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.csproj deleted file mode 100644 index bba922bb53a16..0000000000000 --- a/src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.csproj +++ /dev/null @@ -1,59 +0,0 @@ - - - Microsoft.VisualStudio.LanguageServer.Protocol - - - - - - - Microsoft.VisualStudio.LanguageServer.Protocol - netstandard2.0 - true - LanguageServer.Protocol.ruleset - false - true - true - - - true - A .NET implementation of the Language Server Protocol - $(Summary) - false - true - true - Microsoft VisualStudio LanguageServer Language Server Protocol VSSDK - $(PackageOutputPath)\nuget-public - true - RS0037,SA1011,1591,8618,CA1704 - en-US - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - True - True - Resources.resx - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - diff --git a/src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.ruleset b/src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.ruleset deleted file mode 100644 index 05183f019ae27..0000000000000 --- a/src/LanguageServer/Protocol/Protocol/LanguageServer.Protocol.ruleset +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/LanguageServer/Protocol/Protocol/README.md b/src/LanguageServer/Protocol/Protocol/README.md new file mode 100644 index 0000000000000..25d3a5d5c6c8a --- /dev/null +++ b/src/LanguageServer/Protocol/Protocol/README.md @@ -0,0 +1,103 @@ +## Summary +The files in this folder defines C# types for [LSP protocol definitions](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) and custom VS LSP protocol definitions. + +These types are shared via restricted IVTs to Razor and XAML, as they run inside the C# Roslyn LSP server. + +## Breaking Changes +Ensuring that these types are not binary breaking on changes is important, as this dll is shared between Roslyn, Razor, and XAML in both VSCode and VS. They export handlers that are used in our Roslyn LSP server using protocol types. + +In general, the LSP specification itself generally does not make JSON protocol breaking changes. New additions are controlled by capabilities, properties are only added, etc. Most of the time these kinds of changes are not binary breaking for our type definitions either - it's totally fine to add new properties, methods, etc. to our type definitions + +However, some protocol changes can result in binary breaking changes. The main scenario for this is when the protocol changes a property to a union or adds another definition to a union type. For example, if initially the server capabilities type defined a `hoverProvider`: +```json +hoverProvider?: boolean; +``` +In our C# type definitions this would be defined as +```csharp +[JsonPropertyName("hoverProvider")] +[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +public bool? HoverProvider +{ + get; + set; +} +``` + +It is totally legal (and not breaking) in the LSP protocol to modify this type definition into a union type, controlling what is defined based on a capability. +```json +hoverProvider?: boolean | HoverOptions; +``` +and in C# this would be defined as: +```csharp +[JsonPropertyName("hoverProvider")] +[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +public SumType? HoverProvider +{ + get; + set; +} +``` + +which is now a breaking change due to the property type changing from `bool?` to `SumType?`. And this same logic applies to adding a new value to a union type as going from `SumType` to `SumType` is also a breaking change. + +### Handling breaking changes + +Generally, changes that cause binary breaking changes are relatively rare (adding new types to a union). Additionally, we only need to be careful about binary breaking changes if our partners actually use the API being changed. If no one uses it, we can just update the property with a breaking change. + +However, if a partner is using the type, we need to handle breaking changes to it carefully. We can support this by adding a new intermediate property for the new union type definition, obsoleting the old one, switching our partners to the new property, then move everything back: + +### 1. Add new intermediate property. +First, we add a new property representing the union version of the type and move the serialization attributes to it. The old property is then implemented by accessing the correct value of the new union type. This is safe as the new `HoverOptions` is not provided unless explicitly opted in via capabilities. + +```csharp +[JsonIgnore] +[Obsolete("Use HoverProviderUnion instead")] +public bool? HoverProvider +{ + get => HoverProviderUnion?.First; + set => HoverProviderUnion = value; +} + +[JsonPropertyName("hoverProvider")] +[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +public SumType? HoverProviderUnion +{ + get; + set; +} +``` + +### 2. Update partners to new version. +Update Razor / XAML to consume the new union type property. + +### 3. Switch original property to use union type, obsolete intermediate property. +After partners have switched, we can now change the type of the original property and obsolete the intermediate one: +```csharp +[JsonPropertyName("hoverProvider")] +[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +public SumType? HoverProvider +{ + get; + set; +} + +[JsonIgnore] +[Obsolete("Use HoverProvider instead")] +public SumType? HoverProviderUnion +{ + get => HoverProvider?.First; + set => HoverProvider = value; +} +``` + +### 4. Delete the intermediate property. +After partners have switched again, we can delete the intermediate property. +```csharp +[JsonPropertyName("hoverProvider")] +[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +public SumType? HoverProvider +{ + get; + set; +} +``` \ No newline at end of file