Fix #1399 - crash on start-end syntax for void element

We weren't correctly recovering when a void element is written as a
start-end pair. This change cleans up some of the plumbing around
end-tag handling and adds recognition for this case.

Added a new bespoke diagnostic for the void element case.
This commit is contained in:
Ryan Nowak 2018-09-20 14:02:03 -07:00
parent f601b6d3d2
commit 4f51d90157
4 changed files with 89 additions and 43 deletions

View File

@ -61,6 +61,16 @@ namespace Microsoft.AspNetCore.Blazor.Razor
return RazorDiagnostic.Create(MismatchedClosingTag, span ?? SourceSpan.Undefined, expectedTagName, tagName); return RazorDiagnostic.Create(MismatchedClosingTag, span ?? SourceSpan.Undefined, expectedTagName, tagName);
} }
public static readonly RazorDiagnosticDescriptor UnexpectedClosingTagForVoidElement = new RazorDiagnosticDescriptor(
"BL9983",
() => "Unexpected closing tag '{0}'. The element '{0}' is a void element, and should be used without a closing tag.",
RazorDiagnosticSeverity.Error);
public static RazorDiagnostic Create_UnexpectedClosingTagForVoidElement(SourceSpan? span, string tagName)
{
return RazorDiagnostic.Create(UnexpectedClosingTagForVoidElement, span ?? SourceSpan.Undefined, tagName);
}
public static readonly RazorDiagnosticDescriptor InvalidHtmlContent = new RazorDiagnosticDescriptor( public static readonly RazorDiagnosticDescriptor InvalidHtmlContent = new RazorDiagnosticDescriptor(
"BL9984", "BL9984",
() => "Found invalid HTML content. Text '{0}'", () => "Found invalid HTML content. Text '{0}'",

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
{ {
// Per the HTML spec, the following elements are inherently self-closing // Per the HTML spec, the following elements are inherently self-closing
// For example, <img> is the same as <img /> (and therefore it cannot contain descendants) // For example, <img> is the same as <img /> (and therefore it cannot contain descendants)
public readonly static HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase) public static readonly HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr", "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr",
}; };
@ -182,7 +182,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
} }
case HtmlTokenType.StartTag: case HtmlTokenType.StartTag:
case HtmlTokenType.EndTag:
{ {
var tag = token.AsTag(); var tag = token.AsTag();
@ -218,8 +217,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
stack.Pop(); stack.Pop();
} }
if (token.Type == HtmlTokenType.EndTag) break;
}
case HtmlTokenType.EndTag:
{ {
var tag = token.AsTag();
var popped = stack.Pop(); var popped = stack.Pop();
if (stack.Count == 0) if (stack.Count == 0)
{ {
@ -227,9 +231,12 @@ namespace Microsoft.AspNetCore.Blazor.Razor
// Put back the current node so we don't crash. // Put back the current node so we don't crash.
stack.Push(popped); stack.Push(popped);
var tagName = parser.GetTagNameOriginalCasing(token.AsTag()); var tagName = parser.GetTagNameOriginalCasing(tag);
var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex); var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
var diagnostic = BlazorDiagnosticFactory.Create_UnexpectedClosingTag(span, tagName);
var diagnostic = VoidElements.Contains(tagName)
? BlazorDiagnosticFactory.Create_UnexpectedClosingTagForVoidElement(span, tagName)
: BlazorDiagnosticFactory.Create_UnexpectedClosingTag(span, tagName);
popped.Children.Add(new HtmlElementIntermediateNode() popped.Children.Add(new HtmlElementIntermediateNode()
{ {
Diagnostics = Diagnostics =
@ -260,7 +267,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
popped.Source.Value.CharacterIndex, popped.Source.Value.CharacterIndex,
length); length);
} }
}
break; break;
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit; using Xunit;

View File

@ -255,6 +255,36 @@ namespace Microsoft.AspNetCore.Blazor.Razor
c => NodeAssert.Content(c, "Hello, World!")); c => NodeAssert.Content(c, "Hello, World!"));
} }
[Fact]
public void Execute_RewritesHtml_UnbalancedClosing_MisuseOfVoidElement()
{
// Arrange
var document = CreateDocument(@"<input></input>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var method = documentNode.FindPrimaryMethod();
Assert.Collection(
method.Children,
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
c => NodeAssert.Element(c, "input"),
c => NodeAssert.Element(c, "input"));
var input2 = NodeAssert.Element(method.Children[2], "input");
Assert.Equal(7, input2.Source.Value.AbsoluteIndex);
Assert.Equal(0, input2.Source.Value.LineIndex);
Assert.Equal(7, input2.Source.Value.CharacterIndex);
Assert.Equal(8, input2.Source.Value.Length);
var diagnostic = Assert.Single(input2.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.UnexpectedClosingTagForVoidElement.Id, diagnostic.Id);
Assert.Equal(input2.Source, diagnostic.Span);
}
[Fact] [Fact]
public void Execute_RewritesHtml_UnbalancedClosingTagAtTopLevel() public void Execute_RewritesHtml_UnbalancedClosingTagAtTopLevel()
{ {