diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
index 769244aa28..99e9ad0d41 100644
--- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
@@ -8,6 +8,7 @@ using System.Text;
using AngleSharp;
using AngleSharp.Html;
using AngleSharp.Parser.Html;
+using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@@ -286,7 +287,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor
{
_scopeStack.CloseScope(
tagName: isComponent ? tagNameOriginalCase : nextTag.Data,
- isComponent: isComponent);
+ isComponent: isComponent,
+ source: CalculateSourcePosition(node.Source, nextToken.Position));
var closeMethodName = isComponent
? nameof(RenderTreeBuilder.CloseComponent)
: nameof(RenderTreeBuilder.CloseElement);
@@ -315,6 +317,28 @@ namespace Microsoft.AspNetCore.Blazor.Razor
}
}
+ private SourceSpan? CalculateSourcePosition(
+ SourceSpan? razorTokenPosition,
+ TextPosition htmlNodePosition)
+ {
+ if (razorTokenPosition.HasValue)
+ {
+ var razorPos = razorTokenPosition.Value;
+ return new SourceSpan(
+ razorPos.FilePath,
+ razorPos.AbsoluteIndex + htmlNodePosition.Position,
+ razorPos.LineIndex + htmlNodePosition.Line - 1,
+ htmlNodePosition.Line == 1
+ ? razorPos.CharacterIndex + htmlNodePosition.Column - 1
+ : htmlNodePosition.Column - 1,
+ length: 1);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
private static string GetTagNameWithOriginalCase(string document, HtmlTagToken tagToken)
{
var offset = tagToken.Type == HtmlTokenType.EndTag ? 1 : 0; // For end tags, skip the '/'
diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RazorCompilerException.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RazorCompilerException.cs
index 9061ad1aed..4e5d7a95dd 100644
--- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RazorCompilerException.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RazorCompilerException.cs
@@ -1,6 +1,7 @@
// 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.
+using Microsoft.AspNetCore.Razor.Language;
using System;
namespace Microsoft.AspNetCore.Blazor.Razor
@@ -11,16 +12,25 @@ namespace Microsoft.AspNetCore.Blazor.Razor
///
public class RazorCompilerException : Exception
{
- public RazorCompilerException(string message) : base(message)
+ private readonly int _line;
+ private readonly int _column;
+
+ public RazorCompilerException(string message) : this(message, null)
{
}
+ public RazorCompilerException(string message, SourceSpan? source) : base(message)
+ {
+ _line = source.HasValue ? (source.Value.LineIndex + 1) : 1;
+ _column = source.HasValue ? (source.Value.CharacterIndex + 1) : 1;
+ }
+
public RazorCompilerDiagnostic ToDiagnostic(string sourceFilePath)
=> new RazorCompilerDiagnostic(
RazorCompilerDiagnostic.DiagnosticType.Error,
sourceFilePath,
- line: 1, // Later it might be necessary to take line/col constructor args, but not needed yet
- column: 1,
+ line: _line,
+ column: _column,
message: Message);
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ScopeStack.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ScopeStack.cs
index 70b1eb28b6..abc543a36c 100644
--- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ScopeStack.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ScopeStack.cs
@@ -21,19 +21,19 @@ namespace Microsoft.AspNetCore.Blazor.Razor
_stack.Push(new ScopeEntry(tagName, isComponent));
}
- public void CloseScope(string tagName, bool isComponent)
+ public void CloseScope(string tagName, bool isComponent, SourceSpan? source)
{
if (_stack.Count == 0)
{
throw new RazorCompilerException(
- $"Unexpected closing tag '{tagName}' with no matching start tag.");
+ $"Unexpected closing tag '{tagName}' with no matching start tag.", source);
}
var expected = _stack.Pop();
if (!tagName.Equals(expected.TagName, StringComparison.Ordinal))
{
throw new RazorCompilerException(
- $"Mismatching closing tag. Found '{tagName}' but expected '{expected.TagName}'.");
+ $"Mismatching closing tag. Found '{tagName}' but expected '{expected.TagName}'.", source);
}
// Note: there's no unit test to cover the following, because there's no known way of
diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
index dc5934df37..9a91cb5bef 100644
--- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
@@ -525,7 +525,14 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
{
// Arrange/Act
var result = CompileToCSharp(
- "textmore text");
+ $"@{{\n" +
+ $" var abc = 123;\n" +
+ $"}}\n" +
+ $"\n" +
+ $" \n" +
+ $" text\n" +
+ $" more text\n" +
+ $"\n");
// Assert
Assert.Collection(result.Diagnostics,
@@ -533,6 +540,8 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
{
Assert.Equal(RazorCompilerDiagnostic.DiagnosticType.Error, item.Type);
Assert.StartsWith("Mismatching closing tag. Found 'root' but expected 'child'.", item.Message);
+ Assert.Equal(7, item.Line);
+ Assert.Equal(21, item.Column);
});
}