/src/Features/LanguageServer/ProtocolUnitTests/SemanticTokens/SemanticTokensEditsTests.cs

https://github.com/dotnet/roslyn · C# · 243 lines · 162 code · 46 blank · 35 comment · 2 complexity · 03c75a1c14e6e07bf9a3a6a340bc4ef1 MD5 · raw file

  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. #nullable enable
  5. using System.Linq;
  6. using System.Threading.Tasks;
  7. using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens;
  8. using Xunit;
  9. using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
  10. namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.SemanticTokens
  11. {
  12. public class SemanticTokensEditsTests : AbstractSemanticTokensTests
  13. {
  14. /*
  15. * Markup for basic test case:
  16. * // Comment
  17. * static class C { }
  18. */
  19. private static readonly string s_standardCase = @"{|caret:|}// Comment
  20. static class C { }";
  21. /*
  22. * Markup for single line test case:
  23. * // Comment
  24. */
  25. private static readonly string s_singleLineCase = @"{|caret:|}// Comment";
  26. [Fact]
  27. public async Task TestInsertingNewLineInMiddleOfFile()
  28. {
  29. var updatedText = @"// Comment
  30. static class C { }";
  31. using var workspace = CreateTestWorkspace(s_standardCase, out var locations);
  32. var caretLocation = locations["caret"].First();
  33. await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  34. UpdateDocumentText(updatedText, workspace);
  35. var results = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "1");
  36. var expectedEdit = SemanticTokensEditsHandler.GenerateEdit(start: 5, deleteCount: 1, data: new int[] { 2 });
  37. Assert.Equal(expectedEdit, ((LSP.SemanticTokensEdits)results).Edits.First());
  38. Assert.Equal("2", ((LSP.SemanticTokensEdits)results).ResultId);
  39. }
  40. /// <summary>
  41. /// Tests making a deletion from the end of the file.
  42. /// </summary>
  43. [Fact]
  44. public async Task TestGetSemanticTokensEdits_EndDeletionAsync()
  45. {
  46. var updatedText =
  47. @"// Comment";
  48. using var workspace = CreateTestWorkspace(s_standardCase, out var locations);
  49. var caretLocation = locations["caret"].First();
  50. await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  51. UpdateDocumentText(updatedText, workspace);
  52. var results = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "1");
  53. var expectedEdit = SemanticTokensEditsHandler.GenerateEdit(start: 5, deleteCount: 25, data: System.Array.Empty<int>());
  54. Assert.Equal(expectedEdit, ((LSP.SemanticTokensEdits)results).Edits.First());
  55. Assert.Equal("2", ((LSP.SemanticTokensEdits)results).ResultId);
  56. }
  57. /// <summary>
  58. /// Tests making an insertion at the end of the file.
  59. /// </summary>
  60. [Fact]
  61. public async Task TestGetSemanticTokensEdits_EndInsertionAsync()
  62. {
  63. var updatedText =
  64. @"// Comment
  65. static class C { }
  66. // Comment";
  67. using var workspace = CreateTestWorkspace(s_standardCase, out var locations);
  68. var caretLocation = locations["caret"].First();
  69. await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  70. UpdateDocumentText(updatedText, workspace);
  71. var results = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "1");
  72. var expectedEdit = SemanticTokensEditsHandler.GenerateEdit(
  73. start: 30, deleteCount: 0, data: new int[] { 1, 0, 10, SemanticTokensCache.TokenTypeToIndex[LSP.SemanticTokenTypes.Comment], 0 });
  74. Assert.Equal(expectedEdit, ((LSP.SemanticTokensEdits)results).Edits.First());
  75. Assert.Equal("2", ((LSP.SemanticTokensEdits)results).ResultId);
  76. }
  77. /// <summary>
  78. /// Tests to make sure we return a minimal number of edits.
  79. /// </summary>
  80. [Fact]
  81. public async Task TestGetSemanticTokensEdits_ReturnMinimalEdits()
  82. {
  83. var updatedText =
  84. @"class
  85. // Comment";
  86. using var workspace = CreateTestWorkspace(s_singleLineCase, out var locations);
  87. var caretLocation = locations["caret"].First();
  88. await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  89. // Edit text
  90. UpdateDocumentText(updatedText, workspace);
  91. var results = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "1");
  92. // 1. Replaces length of token (10 to 5) and replaces token type (comment to keyword)
  93. // 2. Creates new token for '// Comment'
  94. var expectedEdit = SemanticTokensEditsHandler.GenerateEdit(
  95. start: 0, deleteCount: 5,
  96. data: new int[]
  97. {
  98. 0, 0, 5, SemanticTokensCache.TokenTypeToIndex[LSP.SemanticTokenTypes.Keyword], 0,
  99. 1, 0, 10, SemanticTokensCache.TokenTypeToIndex[LSP.SemanticTokenTypes.Comment], 0
  100. });
  101. Assert.Equal(expectedEdit, ((LSP.SemanticTokensEdits)results).Edits?[0]);
  102. Assert.Equal("2", ((LSP.SemanticTokensEdits)results).ResultId);
  103. }
  104. /// <summary>
  105. /// Tests to make sure that if we don't have a matching semantic token set for the document in the cache,
  106. /// we return the full set of semantic tokens.
  107. /// </summary>
  108. [Fact]
  109. public async Task TestGetSemanticTokensEditsNoCacheAsync()
  110. {
  111. var updatedText =
  112. @"// Comment
  113. static class C { }";
  114. using var workspace = CreateTestWorkspace(s_standardCase, out var locations);
  115. var caretLocation = locations["caret"].First();
  116. await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  117. UpdateDocumentText(updatedText, workspace);
  118. var results = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "10");
  119. // Make sure we're returned SemanticTokens instead of SemanticTokensEdits.
  120. Assert.True(results.Value is LSP.SemanticTokens);
  121. }
  122. [Fact]
  123. public async Task TestConvertSemanticTokenEditsIntoSemanticTokens_InsertNewlineInMiddleOfFile()
  124. {
  125. var updatedText =
  126. @"// Comment
  127. static class C { }";
  128. using var workspace = CreateTestWorkspace(s_standardCase, out var locations);
  129. var caretLocation = locations["caret"].First();
  130. var originalTokens = await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  131. UpdateDocumentText(updatedText, workspace);
  132. // Edits to tokens conversion
  133. var edits = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "1");
  134. var editsToTokens = ApplySemanticTokensEdits(originalTokens.Data, (LSP.SemanticTokensEdits)edits);
  135. // Raw tokens
  136. var rawTokens = await RunGetSemanticTokensAsync(workspace.CurrentSolution, locations["caret"].First());
  137. Assert.True(Enumerable.SequenceEqual(rawTokens.Data, editsToTokens));
  138. }
  139. [Fact]
  140. public async Task TestConvertSemanticTokenEditsIntoSemanticTokens_ReplacementEdit()
  141. {
  142. var updatedText =
  143. @"// Comment
  144. internal struct S { }";
  145. using var workspace = CreateTestWorkspace(s_standardCase, out var locations);
  146. var caretLocation = locations["caret"].First();
  147. var originalTokens = await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  148. UpdateDocumentText(updatedText, workspace);
  149. // Edits to tokens conversion
  150. var edits = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "1");
  151. var editsToTokens = ApplySemanticTokensEdits(originalTokens.Data, (LSP.SemanticTokensEdits)edits);
  152. // Raw tokens
  153. var rawTokens = await RunGetSemanticTokensAsync(workspace.CurrentSolution, locations["caret"].First());
  154. Assert.True(Enumerable.SequenceEqual(rawTokens.Data, editsToTokens));
  155. }
  156. [Fact]
  157. public async Task TestConvertSemanticTokenEditsIntoSemanticTokens_ManyEdits()
  158. {
  159. var updatedText =
  160. @"
  161. // Comment
  162. class C
  163. {
  164. static void M(int x)
  165. {
  166. var v = 1;
  167. }
  168. }";
  169. using var workspace = CreateTestWorkspace(s_standardCase, out var locations);
  170. var caretLocation = locations["caret"].First();
  171. var originalTokens = await RunGetSemanticTokensAsync(workspace.CurrentSolution, caretLocation);
  172. UpdateDocumentText(updatedText, workspace);
  173. // Edits to tokens conversion
  174. var edits = await RunGetSemanticTokensEditsAsync(workspace.CurrentSolution, caretLocation, previousResultId: "1");
  175. var editsToTokens = ApplySemanticTokensEdits(originalTokens.Data, (LSP.SemanticTokensEdits)edits);
  176. // Raw tokens
  177. var rawTokens = await RunGetSemanticTokensAsync(workspace.CurrentSolution, locations["caret"].First());
  178. Assert.True(Enumerable.SequenceEqual(rawTokens.Data, editsToTokens));
  179. }
  180. private static int[] ApplySemanticTokensEdits(int[]? originalTokens, LSP.SemanticTokensEdits edits)
  181. {
  182. var data = originalTokens.ToList();
  183. if (edits.Edits != null)
  184. {
  185. foreach (var edit in edits.Edits)
  186. {
  187. data.RemoveRange(edit.Start, edit.DeleteCount);
  188. data.InsertRange(edit.Start, edit.Data);
  189. }
  190. }
  191. return data.ToArray();
  192. }
  193. }
  194. }