Skip to content

Add tests for duplicate semantic tokens #1485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 26, 2021
93 changes: 93 additions & 0 deletions src/Bicep.LangServer.IntegrationTests/SemanticTokenTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Bicep.Core.Samples;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using System.Linq;
using FluentAssertions;
using Bicep.Core.Text;
using Bicep.Core.Parsing;
using FluentAssertions.Execution;
using Bicep.LangServer.IntegrationTests.Assertions;
using Bicep.LanguageServer.Extensions;
using Bicep.Core.Syntax;

namespace Bicep.LangServer.IntegrationTests
{
[TestClass]
[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "Test methods do not need to follow this convention.")]
public class SemanticTokenTests
{
[NotNull]
public TestContext? TestContext { get; set; }

[DataTestMethod]
[DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))]
public async Task Overlapping_tokens_are_not_returned(DataSet dataSet)
{
var uri = DocumentUri.From($"/{dataSet.Name}");
var syntaxTree = SyntaxTree.Create(uri.ToUri(), dataSet.Bicep);

using var client = await IntegrationTestHelper.StartServerWithTextAsync(TestContext, dataSet.Bicep, uri);

var semanticTokens = await client.TextDocument.RequestSemanticTokens(new SemanticTokensParams
{
TextDocument = new TextDocumentIdentifier(uri),
});

var tokenSpans = CalculateTokenTextSpans(syntaxTree.LineStarts, semanticTokens!.Data).ToArray();

for (var i = 1; i < tokenSpans.Length; i++)
{
var currentSpan = tokenSpans[i];
var prevSpan = tokenSpans[i - 1];

if (TextSpan.AreOverlapping(prevSpan, currentSpan))
{
using (new AssertionScope()
.WithAnnotations(syntaxTree, "overlapping tokens", new [] { prevSpan, currentSpan }, _ => "here", x => x.ToRange(syntaxTree.LineStarts)))
{
TextSpan.AreOverlapping(prevSpan, currentSpan).Should().BeFalse();
}
}
}
}

private static IEnumerable<TextSpan> CalculateTokenTextSpans(IReadOnlyList<int> lineStarts, IEnumerable<int> semanticTokenData)
{
var lineDeltas = semanticTokenData.Where((x, i) => i % 5 == 0).ToArray();
var charDeltas = semanticTokenData.Where((x, i) => i % 5 == 1).ToArray();
var lengths = semanticTokenData.Where((x, i) => i % 5 == 2).ToArray();

var currentLine = 0;
var currentChar = 0;

for (var i = 0; i < lineDeltas.Length; i++)
{
if (lineDeltas[i] > 0)
{
currentLine += lineDeltas[i];
currentChar = charDeltas[i];
}
else
{
currentChar += charDeltas[i];
}

var position = TextCoordinateConverter.GetOffset(lineStarts, currentLine, currentChar);
var length = lengths[i];

yield return new TextSpan(position, length);
}
}

private static IEnumerable<object[]> GetData()
=> DataSets.AllDataSets.ToDynamicTestData();
}
}