Skip to content

Commit

Permalink
Use LanguageClientFile to simplify test
Browse files Browse the repository at this point in the history
  • Loading branch information
shenglol committed Jan 30, 2025
1 parent 5280316 commit 0c91fd9
Show file tree
Hide file tree
Showing 22 changed files with 333 additions and 348 deletions.
10 changes: 10 additions & 0 deletions src/Bicep.Core.UnitTests/Assertions/AssertionScopeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using Bicep.Core.Diagnostics;
using Bicep.Core.Extensions;
using Bicep.Core.Parsing;
Expand Down Expand Up @@ -58,5 +59,14 @@ public static AssertionScope WithAnnotatedSource(AssertionScope assertionScope,

return assertionScope;
}

public static AssertionScope WithAnnotatedSource(AssertionScope assertionScope, string fileText, ImmutableArray<int> lineStarts, string contextName, IEnumerable<PrintHelper.Annotation> annotations)
{
assertionScope.AddReportable(
contextName,
() => PrintHelper.PrintWithAnnotations(fileText, lineStarts, annotations, 1, true));

return assertionScope;
}
}
}
12 changes: 8 additions & 4 deletions src/Bicep.Core.UnitTests/Utils/PrintHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using System.Text;
using Bicep.Core.Parsing;
using Bicep.Core.PrettyPrintV2;
Expand Down Expand Up @@ -48,27 +49,30 @@ public static string PrintFullSource(BicepSourceFile bicepFile, int context, boo
return string.Join("\n", [.. GetProgramTextLines(bicepFile)]);
}

public static string PrintWithAnnotations(BicepSourceFile bicepFile, IEnumerable<Annotation> annotations, int context, bool includeLineNumbers)
public static string PrintWithAnnotations(BicepSourceFile bicepFile, IEnumerable<Annotation> annotations, int context, bool includeLineNumbers) =>
PrintWithAnnotations(bicepFile.ProgramSyntax.ToString(), bicepFile.LineStarts, annotations, context, includeLineNumbers);

public static string PrintWithAnnotations(string fileText, ImmutableArray<int> lineStarts, IEnumerable<Annotation> annotations, int context, bool includeLineNumbers)
{
var annotationPositions = annotations.ToDictionary(
x => x,
x => TextCoordinateConverter.GetPosition(bicepFile.LineStarts, x.Span.Position));
x => TextCoordinateConverter.GetPosition(lineStarts, x.Span.Position));

if (annotationPositions.Count == 0)
{
return "";
}

var output = new StringBuilder();
var programLines = GetProgramTextLines(bicepFile);
var programLines = StringUtils.SplitOnNewLine(fileText).ToArray();

var annotationsByLine = annotationPositions.ToLookup(x => x.Value.line, x => x.Key);

var minLine = annotationPositions.Values.Aggregate(int.MaxValue, (min, curr) => Math.Min(curr.line, min));
var maxLine = annotationPositions.Values.Aggregate(0, (max, curr) => Math.Max(curr.line, max)) + 1;

minLine = Math.Max(0, minLine - context);
maxLine = Math.Min(bicepFile.LineStarts.Length, maxLine + context);
maxLine = Math.Min(lineStarts.Length, maxLine + context);
var digits = maxLine.ToString().Length;

for (var i = minLine; i < maxLine; i++)
Expand Down
5 changes: 1 addition & 4 deletions src/Bicep.Core/Parsing/StringUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,7 @@ public static string NormalizeNewlines(this string value) =>
ReplaceNewlines(value, "\n");

public static IEnumerable<string> SplitOnNewLine(string value) =>
value.Split(
new string[] { "\r\n", "\r", "\n" },
StringSplitOptions.None
);
value.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);

public static string NormalizeIndent(string value)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Bicep.Core/Workspaces/SourceFileFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static ISourceFile CreateSourceFile(Uri fileUri, string fileContents, Typ
return CreateBicepParamFile(fileUri, fileContents);
}

// The file does not have an extension. Assuming it is a Bicep file. ote that
// The file does not have an extension. Assuming it is a Bicep file. Note that
// this is only possible when a module reference path is provided without an
// extension. When a an untilted file (whose URI has no extension) in VS Code,
// sourceFileType will be set by BicepCompilationManager.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,20 @@
using Bicep.Core.Text;
using Bicep.Core.UnitTests.Utils;
using Bicep.Core.Workspaces;
using Bicep.LangServer.IntegrationTests.Helpers;
using FluentAssertions.Execution;
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;

namespace Bicep.LangServer.IntegrationTests.Assertions
{
public static class AssertionScopeExtensions
{
public static AssertionScope WithAnnotations<T>(this AssertionScope assertionScope, BicepSourceFile bicepFile, string contextName, IEnumerable<T>? data, Func<T, string> messageFunc, Func<T, Range> rangeFunc)
public static AssertionScope WithAnnotations<T>(this AssertionScope assertionScope, LanguageClientFile bicepFile, string contextName, IEnumerable<T>? data, Func<T, string> messageFunc, Func<T, Range> rangeFunc)
=> Core.UnitTests.Assertions.AssertionScopeExtensions.WithAnnotatedSource(
assertionScope,
bicepFile,
bicepFile.Text,
bicepFile.LineStarts,
contextName,
(data ?? []).Select(x => new PrintHelper.Annotation(FromRange(bicepFile, rangeFunc(x)), messageFunc(x))));

public static TextSpan FromRange(BicepSourceFile bicepFile, Range range)
{
var position = TextCoordinateConverter.GetOffset(bicepFile.LineStarts, range.Start.Line, range.Start.Character);
var length = TextCoordinateConverter.GetOffset(bicepFile.LineStarts, range.End.Line, range.End.Character) - position;

return new TextSpan(position, length);
}
(data ?? []).Select(x => new PrintHelper.Annotation(bicepFile.GetSpan(rangeFunc(x)), messageFunc(x))));
}
}
24 changes: 10 additions & 14 deletions src/Bicep.LangServer.IntegrationTests/CodeActionTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ public static async Task ClassCleanup()
await ServerWithNamespaceProvider.DisposeAsync();
}

protected async Task<(CodeAction[] codeActions, BicepFile bicepFile)> GetCodeActionsForSyntaxTest(string fileWithCursors, char emptyCursor = '|', string escapedCursor = "||", MultiFileLanguageServerHelper? server = null)
protected async Task<(CodeAction[] codeActions, LanguageClientFile bicepFile)> GetCodeActionsForSyntaxTest(string fileWithCursors, char emptyCursor = '|', string escapedCursor = "||", MultiFileLanguageServerHelper? server = null)
{
Trace.WriteLine("Input bicep:\n" + fileWithCursors + "\n");

var (file, selection) = ParserHelper.GetFileWithSingleSelection(fileWithCursors, emptyCursor.ToString(), escapedCursor);
var bicepFile = SourceFileFactory.CreateBicepFile(new Uri($"file:///{TestContext.TestName}_{Guid.NewGuid():D}/main.bicep"), file);
var (fileText, selection) = ParserHelper.GetFileWithSingleSelection(fileWithCursors, emptyCursor.ToString(), escapedCursor);
var bicepFile = new LanguageClientFile(DocumentUri.From($"file:///{TestContext.TestName}_{Guid.NewGuid():D}/main.bicep"), fileText);

server ??= await DefaultServer.GetAsync();
await server.OpenFileOnceAsync(TestContext, file, bicepFile.Uri);
await server.OpenFileOnceAsync(TestContext, fileText, bicepFile.Uri);

var codeActions = await RequestCodeActions(server.Client, bicepFile, selection);
return (codeActions.ToArray(), bicepFile);
Expand Down Expand Up @@ -122,29 +122,25 @@ protected static IEnumerable<TextSpan> GetOverlappingSpans(TextSpan span)
yield return new TextSpan(startOffset, span.Length + 1);
}

protected static async Task<IEnumerable<CodeAction>> RequestCodeActions(ILanguageClient client, BicepFile bicepFile, TextSpan span)
protected static async Task<IEnumerable<CodeAction>> RequestCodeActions(ILanguageClient client, LanguageClientFile bicepFile, TextSpan span)
{
var startPosition = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, span.Position);
var endPosition = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, span.Position + span.Length);
endPosition.Should().BeGreaterThanOrEqualTo(startPosition);

var result = await client.RequestCodeAction(new CodeActionParams
{
TextDocument = new TextDocumentIdentifier(bicepFile.Uri),
Range = new Range(startPosition, endPosition),
TextDocument = bicepFile.Uri,
Range = bicepFile.GetRange(span),
});

return result!.Select(x => x.CodeAction).WhereNotNull();
}

protected static BicepFile ApplyCodeAction(BicepFile bicepFile, CodeAction codeAction)
protected static LanguageClientFile ApplyCodeAction(LanguageClientFile bicepFile, CodeAction codeAction)
{
// only support a small subset of possible edits for now - can always expand this later on
codeAction.Edit!.Changes.Should().NotBeNull();
codeAction.Edit.Changes.Should().HaveCount(1);
codeAction.Edit.Changes.Should().ContainKey(bicepFile.Uri);

var bicepText = bicepFile.ProgramSyntax.ToString();
var bicepText = bicepFile.Text;
var changes = codeAction.Edit.Changes![bicepFile.Uri].ToArray();

for (int i = 0; i < changes.Length; ++i)
Expand Down Expand Up @@ -204,7 +200,7 @@ protected static BicepFile ApplyCodeAction(BicepFile bicepFile, CodeAction codeA
"Rename should be positioned on the new identifier right after 'var ' or 'param ' or 'type '");
}

return SourceFileFactory.CreateBicepFile(bicepFile.Uri, bicepText);
return new LanguageClientFile(bicepFile.Uri, bicepText);
}
}
}
35 changes: 11 additions & 24 deletions src/Bicep.LangServer.IntegrationTests/CodeActionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Bicep.Core.UnitTests.Serialization;
using Bicep.Core.UnitTests.Utils;
using Bicep.Core.Workspaces;
using Bicep.LangServer.IntegrationTests.Assertions;
using Bicep.LangServer.IntegrationTests.Helpers;
using Bicep.LanguageServer.Extensions;
using Bicep.LanguageServer.Utils;
Expand Down Expand Up @@ -76,17 +77,17 @@ public async Task RequestingCodeActionWithFixableDiagnosticsShouldProduceQuickFi
// Assert.
quickFixes.Should().NotBeNull();

var spansOverlapOrAbut = (IFixable f) =>
bool SpansOverlapOrAbut(IFixable f)
{
if (span.Position <= f.Span.Position)
{
return span.GetEndPosition() >= f.Span.Position;
}

return f.Span.GetEndPosition() >= span.Position;
};
}

var bicepFixes = allFixables.Where(spansOverlapOrAbut).SelectMany(f => f.Fixes).ToHashSet();
var bicepFixes = allFixables.Where(SpansOverlapOrAbut).SelectMany(f => f.Fixes).ToHashSet();
var quickFixList = quickFixes!.Where(x => x.CodeAction?.Kind == CodeActionKind.QuickFix).ToList();

var bicepFixTitles = bicepFixes.Select(f => f.Title);
Expand Down Expand Up @@ -372,10 +373,12 @@ public async Task Parameter_decorator_actions_are_suggested(string type, string
codeActions.First(x => x.Title == title).Kind.Should().Be(CodeActionKind.Refactor);

var updatedFile = ApplyCodeAction(bicepFile, codeActions.Single(x => x.Title == title));
updatedFile.Should().HaveSourceText($@"
{decorator}
param foo {type}
");
updatedFile.Should().HaveSourceText($"""
{decorator}
param foo {type}
""");
}

[DataRow("string", "@secure()", SecureTitle)]
Expand Down Expand Up @@ -532,7 +535,7 @@ public async Task Unused_parameter_actions_are_not_suggested_for_invalid_paramet
codeActions.Should().NotContain(x => x.Title.StartsWith(RemoveUnusedParameterTitle));
}

private async Task<(IEnumerable<CodeAction> codeActions, BicepFile bicepFile)> RunParameterSyntaxTest(string paramType, string? decorator = null)
private async Task<(IEnumerable<CodeAction> codeActions, LanguageClientFile bicepFile)> RunParameterSyntaxTest(string paramType, string? decorator = null)
{
string fileWithCursors = @$"
param fo|o {paramType}
Expand All @@ -553,21 +556,5 @@ private static IEnumerable<object[]> GetData()
{
return DataSets.NonStressDataSets.ToDynamicTestData();
}

private static async Task<BicepFile> FormatDocument(ILanguageClient client, BicepFile bicepFile)
{
var textEditContainer = await client.TextDocument.RequestDocumentFormatting(new DocumentFormattingParams
{
TextDocument = new TextDocumentIdentifier(bicepFile.Uri),
Options = new FormattingOptions
{
TabSize = 2,
InsertSpaces = true,
InsertFinalNewline = true,
},
});

return SourceFileFactory.CreateBicepFile(bicepFile.Uri, textEditContainer!.Single().NewText);
}
}
}
Loading

0 comments on commit 0c91fd9

Please sign in to comment.