Fix render fragment formatting in C# blocks

This commit is contained in:
David Wengier 2021-12-06 20:11:36 +11:00
Родитель fd0673153a
Коммит 16a8596c22
3 изменённых файлов: 89 добавлений и 0 удалений

Просмотреть файл

@ -32,6 +32,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
_workspaceFactory = workspaceFactory;
}
public static bool SkipValidateComponents { get; set; }
public DocumentUri Uri { get; private set; } = null!;
public DocumentSnapshot OriginalSnapshot { get; private set; } = null!;
@ -239,6 +241,8 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
[Conditional("DEBUG")]
private static void ValidateComponents(RazorCodeDocument oldCodeDocument, RazorCodeDocument newCodeDocument)
{
if (SkipValidateComponents) return;
var oldTagHelperElements = oldCodeDocument.GetSyntaxTree().Root.DescendantNodesAndSelf().OfType<Language.Syntax.MarkupTagHelperElementSyntax>().Count();
var newTagHelperElements = newCodeDocument.GetSyntaxTree().Root.DescendantNodesAndSelf().OfType<Language.Syntax.MarkupTagHelperElementSyntax>().Count();
Debug.Assert(oldTagHelperElements == newTagHelperElements, $"Previous context had {oldTagHelperElements} components, new only has {newTagHelperElements}.");

Просмотреть файл

@ -125,6 +125,48 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
if (context.Indentations[i].StartsInCSharpContext)
{
// Normal we don't do HTML things in C# contexts but there is one
// edge case when including render fragments in a C# code block, eg:
//
// @code {
// void Foo()
// {
// Render(@<SurveyPrompt />);
// {
// }
//
// This is popular in the bUnit unit testing library. The issue here is that
// the HTML formatter sees ~~~~~<SurveyPrompt /> and puts a newline before
// the tag, but obviously that breaks things.
//
// It's straight forward enough to just check for this situation and special case
// it by removing the newline again.
// There needs to be at least one more line, and the current line needs to end with
// an @ sign, and have an open angle bracket at the start of the next line.
if (sourceText.Lines.Count >= i + 1 &&
line.Text?.Length > 1 &&
line.Text?[line.End - 1] == '@')
{
var nextLine = sourceText.Lines[i + 1];
var firstChar = nextLine.GetFirstNonWhitespaceOffset().GetValueOrDefault();
// When the HTML formatter inserts the newline in this scenario, it doesn't
// indent the component tag, so we use that as another signal that this is
// the scenario we think it is.
if (firstChar == 0 &&
nextLine.Text?[nextLine.Start] == '<')
{
var lineBreakLength = line.EndIncludingLineBreak - line.End;
var spanToReplace = new TextSpan(line.End, lineBreakLength);
var change = new TextChange(spanToReplace, string.Empty);
editsToApply.Add(change);
// Skip the next line because we've essentially just removed it.
i++;
}
}
continue;
}

Просмотреть файл

@ -823,6 +823,49 @@ else
");
}
[Theory]
[CombinatorialData]
[WorkItem("https://github.com/dotnet/razor-tooling/issues/5749")]
public async Task FormatRenderFragmentInCSharpCodeBlock(bool useSourceTextDiffer)
{
// Sadly the first thing the HTML formatter does with this input
// is put a newline after the @, which means <SurveyPrompt /> won't be
// seen as a component any more, so we have to turn off our validation,
// or the test fails before we have a chance to fix the formatting.
FormattingContext.SkipValidateComponents = true;
await RunFormattingTestAsync(useSourceTextDiffer: useSourceTextDiffer,
input: @"
@code
{
public void DoStuff(RenderFragment renderFragment)
{
renderFragment(@<SurveyPrompt Title=""Foo"" />);
@* comment *@
<div></div>
@* comment *@<div></div>
}
}
",
expected: @"@code
{
public void DoStuff(RenderFragment renderFragment)
{
renderFragment(@<SurveyPrompt Title=""Foo"" />);
@* comment *@
<div></div>
@* comment *@
<div></div>
}
}
",
tagHelpers: GetSurveyPrompt());
}
private IReadOnlyList<TagHelperDescriptor> GetSurveyPrompt()
{
AdditionalSyntaxTrees.Add(Parse(@"