Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ private async Task<ImmutableArray<TextChange>> FilterIncomingChangesAsync(Format
// there could be one edit to replace the whole line.
changes = SourceTextDiffer.GetMinimalTextChanges(originalText, formattedText, DiffKind.Char);
changes = FilterChangesInUnsupportedSpans(changes, razorCommentSpans);
changes = FilterOutNonWhitespaceChanges(changes);

// Re-apply the changes to get the new formatted text
formattedText = originalText.WithChanges(changes);
Expand Down Expand Up @@ -196,6 +197,54 @@ ImmutableArray<TextChange> FilterChangesInUnsupportedSpans(ImmutableArray<TextCh

return validChanges.ToImmutableAndClear();
}

ImmutableArray<TextChange> FilterOutNonWhitespaceChanges(ImmutableArray<TextChange> candidateChanges)
{
if (candidateChanges.IsEmpty)
{
return candidateChanges;
}

// The character differ can split a collectively safe rewrite into changes that do not compare safely in isolation.
if (originalText.NonWhitespaceContentEquals(candidateChanges))
{
return candidateChanges;
}

var changedText = originalText.WithChanges(candidateChanges);
using var validChanges = new PooledArrayBuilder<TextChange>(capacity: candidateChanges.Length);

var positionDelta = 0;
var previousStart = -1;
foreach (var change in candidateChanges)
{
Debug.Assert(change.Span.Start >= previousStart);
previousStart = change.Span.Start;

var replacementLength = change.NewText?.Length ?? 0;
var changedStart = change.Span.Start + positionDelta;
var changedEnd = changedStart + replacementLength;
if (originalText.NonWhitespaceContentEquals(
changedText,
change.Span.Start,
change.Span.End,
changedStart,
changedEnd))
{
validChanges.Add(change);
}

positionDelta += replacementLength - change.Span.Length;
}

if (candidateChanges.Length == validChanges.Count)
{
return candidateChanges;
}

_logger.LogWarning("Ignoring non-whitespace changes returned by the HTML formatter.");
return validChanges.ToImmutableAndClear();
}
}

private static ImmutableArray<LineInfo> GenerateLineInfo(SourceText originalText, ImmutableArray<TextSpan> scriptAndStyleSpans, ImmutableArray<TextSpan> razorCommentSpans)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,45 @@ @section Scripts {
fileKind: RazorFileKind.Legacy);
}

[Fact]
[WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3040290")]
public async Task IgnoresHtmlFormatterChangesToNonWhitespaceInScript()
{
await RunFormattingTestAsync(
input: """
<script>
@if (showGrid)
{
<text>
const ids = grid.getIds();
</text>
}
</script>
""",
htmlFormatted: """
<script>
@if (showGrid)
{
<text>
t ids = grid.getIds();
</text>
}
</script>
""",
expected: """
<script>
@if (showGrid)
{
<text>
const ids = grid.getIds();
</text>
}
</script>
""",
fileKind: RazorFileKind.Legacy,
validateHtmlFormattedMatchesWebTools: false);
}

[Fact]
public async Task Section_Scripts_ThreeScriptTags()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,100 @@ public async Task RemoveEditThatSplitsStringLiteral_MultiLineDocument(string pre
Assert.Empty(edits);
}

[Fact]
[WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3040290")]
public async Task KeepEditWithEquivalentNonWhitespaceContent()
{
TestCode input = """
<script>
[|var x=2;|]
</script>
""";
var document = CreateProjectAndRazorDocument(input.Text);
var sourceText = SourceText.From(input.Text);
var change = new TextChange(input.Span, "var x = 2;");

var edits = await GetHtmlFormattingEditsAsync(document, change);

AssertEx.EqualOrDiff(
sourceText.WithChanges(change).ToString(),
sourceText.WithChanges(edits).ToString());
}

[Fact]
[WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3040290")]
public async Task KeepMultipleWhitespaceEditsWithLengthChanges()
{
TestCode input = """
<script>
var first=1;
var second = 2;
var third=3;
</script>
""";
var document = CreateProjectAndRazorDocument(input.Text);
var sourceText = SourceText.From(input.Text);
ImmutableArray<TextChange> changes =
[
new(sourceText.Lines[1].Span, " var first = 1;"),
new(sourceText.Lines[2].Span, " var second=2;"),
new(sourceText.Lines[3].Span, " var third = 3;"),
];

var edits = await GetHtmlFormattingEditsAsync(document, changes);

AssertEx.EqualOrDiff(
sourceText.WithChanges(changes).ToString(),
sourceText.WithChanges(edits).ToString());
}

[Theory]
[WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3040290")]
[InlineData("AAAA", "BBBBBBBB")]
[InlineData("BBBBBBBB", "AAAA")]
public async Task KeepWhitespaceOnlyEditsAroundFilteredLengthChangingEdit(string original, string replacement)
{
TestCode input = $$"""
<script>
var first=1;
{{original}}
var third = 3;
</script>
""";
var document = CreateProjectAndRazorDocument(input.Text);
var sourceText = SourceText.From(input.Text);

var firstLine = sourceText.Lines[1];
var firstEquals = firstLine.Start + sourceText.ToString(firstLine.Span).IndexOf('=');
var insertSpaceBeforeFirstEquals = new TextChange(new(firstEquals, 0), " ");
var insertSpaceAfterFirstEquals = new TextChange(new(firstEquals + 1, 0), " ");

var unsafeChange = new TextChange(sourceText.Lines[2].Span, $" {replacement}");

var thirdLine = sourceText.Lines[3];
var thirdEquals = thirdLine.Start + sourceText.ToString(thirdLine.Span).IndexOf('=');
var removeSpaceBeforeThirdEquals = new TextChange(new(thirdEquals - 1, 1), "");
var removeSpaceAfterThirdEquals = new TextChange(new(thirdEquals + 1, 1), "");

var edits = await GetHtmlFormattingEditsAsync(
document,
insertSpaceBeforeFirstEquals,
insertSpaceAfterFirstEquals,
unsafeChange,
removeSpaceBeforeThirdEquals,
removeSpaceAfterThirdEquals);

AssertEx.EqualOrDiff(
$$"""
<script>
var first = 1;
{{original}}
var third = 3;
</script>
""",
sourceText.WithChanges(edits).ToString());
}

[Fact]
public async Task FilterOutHtmlEdits()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ public async Task GameTracAdmin()
public async Task RanOutOfOriginalLinesFullFormatting()
=> Assert.NotNull(await GetFormattingEditsAsync());

[Fact]
[WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3040290")]
public async Task PageForMultiGrid()
=> Assert.NotNull(await GetFormattingEditsAsync());

[Fact]
[WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3040290")]
public async Task PageForGrid()
=> Assert.NotNull(await GetFormattingEditsAsync());

private async Task<TextEdit[]?> GetFormattingEditsAsync([CallerMemberName] string? testName = null)
{
var contents = GetResource(testName.AssumeNotNull(), "InitialDocument.txt").AssumeNotNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3

Large diffs are not rendered by default.

Loading