Change `RazorError`s to consistently provide error lengths.

- Updated `TagHelper` errors to no longer highlight the entire tag as an error, instead just the tag name is marked as an error. This is now consistent with nested tags in `@{ ... }` errors.
- Updated `RazorError` and corresponding error logging constructs to disallow creation without providing lengths.
- Updated `TagHelperDescriptorResolver` and related classes to properly determine assembly locations within directives. This allows for exact error locations in the `@addTagHelper` directive.

#386
This commit is contained in:
N. Taylor Mullen 2015-09-01 21:55:21 -07:00
parent 43fce8c927
commit ad5bfc5b66
15 changed files with 263 additions and 121 deletions

View File

@ -315,7 +315,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
{ {
errorSink.OnError(SourceLocation.Zero, whitespaceError); errorSink.OnError(SourceLocation.Zero, whitespaceError, length: 0);
validName = false; validName = false;
} }
@ -327,7 +327,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
InvalidNonWhitespaceNameCharacters.Contains(character)) InvalidNonWhitespaceNameCharacters.Contains(character))
{ {
var error = characterErrorBuilder(character); var error = characterErrorBuilder(character);
errorSink.OnError(SourceLocation.Zero, error); errorSink.OnError(SourceLocation.Zero, error, length: 0);
validName = false; validName = false;
} }
@ -379,7 +379,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
type.FullName, type.FullName,
property.Name, property.Name,
typeof(HtmlAttributeNameAttribute).FullName, typeof(HtmlAttributeNameAttribute).FullName,
nameof(HtmlAttributeNameAttribute.Name))); nameof(HtmlAttributeNameAttribute.Name)),
length: 0);
continue; continue;
} }
@ -438,7 +439,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
SourceLocation.Zero, SourceLocation.Zero,
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty( Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(
parentType.FullName, parentType.FullName,
attributeDescriptor.PropertyName)); attributeDescriptor.PropertyName),
length: 0);
return false; return false;
} }
@ -492,7 +494,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace( Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(
parentType.FullName, parentType.FullName,
propertyName, propertyName,
nameOrPrefix)); nameOrPrefix),
length: 0);
return false; return false;
} }
@ -508,7 +511,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
propertyName, propertyName,
nameOrPrefix, nameOrPrefix,
attributeNameOrPrefix, attributeNameOrPrefix,
DataDashPrefix)); DataDashPrefix),
length: 0);
return false; return false;
} }
@ -525,7 +529,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
propertyName, propertyName,
nameOrPrefix, nameOrPrefix,
attributeNameOrPrefix, attributeNameOrPrefix,
character)); character),
length: 0);
isValid = false; isValid = false;
} }
@ -576,7 +581,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
property.Name, property.Name,
nameof(HtmlAttributeNameAttribute), nameof(HtmlAttributeNameAttribute),
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix), nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
"IDictionary<string, TValue>")); "IDictionary<string, TValue>"),
length: 0);
} }
else if (attributeNameAttribute != null && !hasPublicSetter) else if (attributeNameAttribute != null && !hasPublicSetter)
{ {
@ -589,7 +595,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
parentType.FullName, parentType.FullName,
property.Name, property.Name,
nameof(HtmlAttributeNameAttribute), nameof(HtmlAttributeNameAttribute),
"IDictionary<string, TValue>")); "IDictionary<string, TValue>"),
length: 0);
} }
return null; return null;
@ -608,7 +615,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
property.Name, property.Name,
nameof(HtmlAttributeNameAttribute), nameof(HtmlAttributeNameAttribute),
nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix), nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
"IDictionary<string, TValue>")); "IDictionary<string, TValue>"),
length: 0);
return null; return null;
} }

View File

@ -81,7 +81,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{ {
var descriptors = ResolveDescriptorsInAssembly( var descriptors = ResolveDescriptorsInAssembly(
lookupInfo.AssemblyName, lookupInfo.AssemblyName,
directiveDescriptor.Location, lookupInfo.AssemblyNameLocation,
context.ErrorSink); context.ErrorSink);
// Only use descriptors that match our lookup info // Only use descriptors that match our lookup info
@ -101,7 +101,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Resources.FormatTagHelperDescriptorResolver_EncounteredUnexpectedError( Resources.FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(
"@" + directiveName, "@" + directiveName,
directiveDescriptor.DirectiveText, directiveDescriptor.DirectiveText,
ex.Message)); ex.Message),
GetErrorLength(directiveDescriptor.DirectiveText));
} }
} }
@ -183,7 +184,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
context.ErrorSink.OnError( context.ErrorSink.OnError(
directive.Location, directive.Location,
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperDirective( Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperDirective(
SyntaxConstants.CSharp.TagHelperPrefixKeyword)); SyntaxConstants.CSharp.TagHelperPrefixKeyword),
GetErrorLength(directive.DirectiveText));
} }
} }
@ -213,7 +215,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperPrefixValue( Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperPrefixValue(
SyntaxConstants.CSharp.TagHelperPrefixKeyword, SyntaxConstants.CSharp.TagHelperPrefixKeyword,
character, character,
prefix)); prefix),
prefix.Length);
return false; return false;
} }
@ -241,8 +244,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return regex.IsMatch(descriptor.TypeName); return regex.IsMatch(descriptor.TypeName);
} }
private static LookupInfo GetLookupInfo(TagHelperDirectiveDescriptor directiveDescriptor, private static LookupInfo GetLookupInfo(
ErrorSink errorSink) TagHelperDirectiveDescriptor directiveDescriptor,
ErrorSink errorSink)
{ {
var lookupText = directiveDescriptor.DirectiveText; var lookupText = directiveDescriptor.DirectiveText;
var lookupStrings = lookupText?.Split(new[] { ',' }); var lookupStrings = lookupText?.Split(new[] { ',' });
@ -256,23 +260,42 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{ {
errorSink.OnError( errorSink.OnError(
directiveDescriptor.Location, directiveDescriptor.Location,
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText)); Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText),
GetErrorLength(lookupText));
return null; return null;
} }
var trimmedAssemblyName = lookupStrings[1].Trim();
// + 1 is for the comma separator in the lookup text.
var assemblyNameIndex = lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName);
var assemblyNamePrefix = directiveDescriptor.DirectiveText.Substring(0, assemblyNameIndex);
var assemblyNameLocation = SourceLocation.Advance(directiveDescriptor.Location, assemblyNamePrefix);
return new LookupInfo return new LookupInfo
{ {
TypePattern = lookupStrings[0].Trim(), TypePattern = lookupStrings[0].Trim(),
AssemblyName = lookupStrings[1].Trim() AssemblyName = trimmedAssemblyName,
AssemblyNameLocation = assemblyNameLocation,
}; };
} }
private static int GetErrorLength(string directiveText)
{
var nonNullLength = directiveText == null ? 1 : directiveText.Length;
var normalizeEmptyStringLength = Math.Max(nonNullLength, 1);
return normalizeEmptyStringLength;
}
private class LookupInfo private class LookupInfo
{ {
public string AssemblyName { get; set; } public string AssemblyName { get; set; }
public string TypePattern { get; set; } public string TypePattern { get; set; }
public SourceLocation AssemblyNameLocation { get; set; }
} }
} }
} }

View File

@ -35,14 +35,18 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <see cref="ITagHelper"/> <see cref="Type"/>s.</param> /// <see cref="ITagHelper"/> <see cref="Type"/>s.</param>
/// <returns>An <see cref="IEnumerable{Type}"/> of valid <see cref="ITagHelper"/> <see cref="Type"/>s. /// <returns>An <see cref="IEnumerable{Type}"/> of valid <see cref="ITagHelper"/> <see cref="Type"/>s.
/// </returns> /// </returns>
public IEnumerable<Type> Resolve(string name, public IEnumerable<Type> Resolve(
SourceLocation documentLocation, string name,
[NotNull] ErrorSink errorSink) SourceLocation documentLocation,
[NotNull] ErrorSink errorSink)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {
errorSink.OnError(documentLocation, var errorLength = name == null ? 1 : Math.Max(name.Length, 1);
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull); errorSink.OnError(
documentLocation,
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
errorLength);
return Type.EmptyTypes; return Type.EmptyTypes;
} }
@ -60,7 +64,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
documentLocation, documentLocation,
Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly( Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(
assemblyName.Name, assemblyName.Name,
ex.Message)); ex.Message),
name.Length);
return Type.EmptyTypes; return Type.EmptyTypes;
} }

View File

@ -40,16 +40,6 @@ namespace Microsoft.AspNet.Razor
_errors.Add(error); _errors.Add(error);
} }
/// <summary>
/// Creates and tracks a new <see cref="RazorError"/>.
/// </summary>
/// <param name="location"><see cref="SourceLocation"/> of the error.</param>
/// <param name="message">A message describing the error.</param>
public void OnError(SourceLocation location, string message)
{
OnError(location, message, RazorError.DefaultErrorLength);
}
/// <summary> /// <summary>
/// Creates and tracks a new <see cref="RazorError"/>. /// Creates and tracks a new <see cref="RazorError"/>.
/// </summary> /// </summary>

View File

@ -102,7 +102,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!errorReported) if (!errorReported)
{ {
errorReported = true; errorReported = true;
Context.OnError(errorLocation, RazorResources.ParseError_MissingOpenBraceAfterSection); Context.OnError(
errorLocation,
RazorResources.ParseError_MissingOpenBraceAfterSection,
length: 1 /* { */);
} }
PutCurrentBack(); PutCurrentBack();
@ -133,9 +136,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!Optional(CSharpSymbolType.RightBrace)) if (!Optional(CSharpSymbolType.RightBrace))
{ {
editHandler.AutoCompleteString = "}"; editHandler.AutoCompleteString = "}";
Context.OnError(CurrentLocation, Context.OnError(
RazorResources.FormatParseError_Expected_X( CurrentLocation,
Language.GetSample(CSharpSymbolType.RightBrace))); RazorResources.FormatParseError_Expected_X(Language.GetSample(CSharpSymbolType.RightBrace)),
length: 1 /* } */);
} }
else else
{ {
@ -160,8 +164,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!At(CSharpSymbolType.LeftBrace)) if (!At(CSharpSymbolType.LeftBrace))
{ {
Context.OnError(CurrentLocation, Context.OnError(
RazorResources.FormatParseError_Expected_X(Language.GetSample(CSharpSymbolType.LeftBrace))); CurrentLocation,
RazorResources.FormatParseError_Expected_X(Language.GetSample(CSharpSymbolType.LeftBrace)),
length: 1 /* { */);
CompleteBlock(); CompleteBlock();
Output(SpanKind.MetaCode); Output(SpanKind.MetaCode);
return; return;
@ -186,7 +192,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!At(CSharpSymbolType.RightBrace)) if (!At(CSharpSymbolType.RightBrace))
{ {
editHandler.AutoCompleteString = "}"; editHandler.AutoCompleteString = "}";
Context.OnError(blockStart, RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, "}", "{")); Context.OnError(
blockStart,
RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, "}", "{"),
length: 1 /* } */);
CompleteBlock(); CompleteBlock();
Output(SpanKind.Code); Output(SpanKind.Code);
} }

View File

@ -29,7 +29,10 @@ namespace Microsoft.AspNet.Razor.Parser
protected virtual void ReservedDirective(bool topLevel) protected virtual void ReservedDirective(bool topLevel)
{ {
Context.OnError(CurrentLocation, RazorResources.FormatParseError_ReservedWord(CurrentSymbol.Content)); Context.OnError(
CurrentLocation,
RazorResources.FormatParseError_ReservedWord(CurrentSymbol.Content),
CurrentSymbol.Content.Length);
AcceptAndMoveNext(); AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.ChunkGenerator = SpanChunkGenerator.Null; Span.ChunkGenerator = SpanChunkGenerator.Null;
@ -109,7 +112,10 @@ namespace Microsoft.AspNet.Razor.Parser
// using Identifier ==> Using Declaration // using Identifier ==> Using Declaration
if (!topLevel) if (!topLevel)
{ {
Context.OnError(block.Start, RazorResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock); Context.OnError(
block.Start,
RazorResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
block.Name.Length);
StandardStatement(); StandardStatement();
} }
else else
@ -327,10 +333,12 @@ namespace Microsoft.AspNet.Razor.Parser
// Check for "{" to make sure we're at a block // Check for "{" to make sure we're at a block
if (!At(CSharpSymbolType.LeftBrace)) if (!At(CSharpSymbolType.LeftBrace))
{ {
Context.OnError(CurrentLocation, Context.OnError(
RazorResources.FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed( CurrentLocation,
Language.GetSample(CSharpSymbolType.LeftBrace), RazorResources.FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed(
CurrentSymbol.Content)); Language.GetSample(CSharpSymbolType.LeftBrace),
CurrentSymbol.Content),
CurrentSymbol.Content.Length);
} }
// Parse the statement and then we're done // Parse the statement and then we're done
@ -480,7 +488,10 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
if (type == CSharpSymbolType.Transition && !isSingleLineMarkup) if (type == CSharpSymbolType.Transition && !isSingleLineMarkup)
{ {
Context.OnError(loc, RazorResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start); Context.OnError(
loc,
RazorResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start,
length: 1 /* @ */);
} }
// Markup block // Markup block
@ -560,7 +571,10 @@ namespace Microsoft.AspNet.Razor.Parser
// Throw errors as necessary, but continue parsing // Throw errors as necessary, but continue parsing
if (At(CSharpSymbolType.LeftBrace)) if (At(CSharpSymbolType.LeftBrace))
{ {
Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_Nested_CodeBlock); Context.OnError(
CurrentLocation,
RazorResources.ParseError_Unexpected_Nested_CodeBlock,
length: 1 /* { */);
} }
// @( or @foo - Nested expression, parse a child block // @( or @foo - Nested expression, parse a child block
@ -649,7 +663,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (EndOfFile) if (EndOfFile)
{ {
Context.OnError(block.Start, RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, '}', '{')); Context.OnError(
block.Start,
RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, '}', '{'),
length: 1 /* { OR } */);
} }
else if (acceptTerminatingBrace) else if (acceptTerminatingBrace)
{ {

View File

@ -256,15 +256,25 @@ namespace Microsoft.AspNet.Razor.Parser
}; };
if (At(CSharpSymbolType.WhiteSpace) || At(CSharpSymbolType.NewLine)) if (At(CSharpSymbolType.WhiteSpace) || At(CSharpSymbolType.NewLine))
{ {
Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS); Context.OnError(
CurrentLocation,
RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS,
CurrentSymbol.Content.Length);
} }
else if (EndOfFile) else if (EndOfFile)
{ {
Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock); Context.OnError(
CurrentLocation,
RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
length: 1 /* end of file */);
} }
else else
{ {
Context.OnError(CurrentLocation, RazorResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS(CurrentSymbol.Content)); Context.OnError(
CurrentLocation,
RazorResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS(
CurrentSymbol.Content),
CurrentSymbol.Content.Length);
} }
} }
finally finally
@ -529,7 +539,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!success) if (!success)
{ {
AcceptUntil(CSharpSymbolType.LessThan); AcceptUntil(CSharpSymbolType.LessThan);
Context.OnError(block.Start, RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, ")", "(")); Context.OnError(
block.Start,
RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, ")", "("),
length: 1 /* ( */);
} }
// If necessary, put an empty-content marker symbol here // If necessary, put an empty-content marker symbol here
@ -556,7 +569,10 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
if (Context.IsWithin(BlockType.Template)) if (Context.IsWithin(BlockType.Template))
{ {
Context.OnError(CurrentLocation, RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested); Context.OnError(
CurrentLocation,
RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested,
length: 1 /* @ */);
} }
Output(SpanKind.Code); Output(SpanKind.Code);
using (Context.StartBlock(BlockType.Template)) using (Context.StartBlock(BlockType.Template))

View File

@ -15,6 +15,8 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
public partial class HtmlMarkupParser public partial class HtmlMarkupParser
{ {
private const string ScriptTagName = "script";
private SourceLocation _lastTagStart = SourceLocation.Zero; private SourceLocation _lastTagStart = SourceLocation.Zero;
private HtmlSymbol _bufferedOpenAngle; private HtmlSymbol _bufferedOpenAngle;
@ -62,7 +64,10 @@ namespace Microsoft.AspNet.Razor.Parser
} }
else else
{ {
Context.OnError(CurrentSymbol.Start, RazorResources.ParseError_MarkupBlock_Must_Start_With_Tag); Context.OnError(
CurrentSymbol.Start,
RazorResources.ParseError_MarkupBlock_Must_Start_With_Tag,
CurrentSymbol.Content.Length);
} }
Output(SpanKind.Markup); Output(SpanKind.Markup);
} }
@ -221,7 +226,10 @@ namespace Microsoft.AspNet.Razor.Parser
} }
if (tags.Count == 0) if (tags.Count == 0)
{ {
Context.OnError(CurrentLocation, RazorResources.ParseError_OuterTagMissingName); Context.OnError(
CurrentLocation,
RazorResources.ParseError_OuterTagMissingName,
length: 1 /* end of file */);
} }
return false; return false;
} }
@ -346,11 +354,10 @@ namespace Microsoft.AspNet.Razor.Parser
private bool EndTextTag(HtmlSymbol solidus, IDisposable tagBlockWrapper) private bool EndTextTag(HtmlSymbol solidus, IDisposable tagBlockWrapper)
{ {
var start = _bufferedOpenAngle.Start;
Accept(_bufferedOpenAngle); Accept(_bufferedOpenAngle);
Accept(solidus); Accept(solidus);
var textLocation = CurrentLocation;
Assert(HtmlSymbolType.Text); Assert(HtmlSymbolType.Text);
AcceptAndMoveNext(); AcceptAndMoveNext();
@ -358,7 +365,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!seenCloseAngle) if (!seenCloseAngle)
{ {
Context.OnError(start, RazorResources.ParseError_TextTagCannotContainAttributes); Context.OnError(
textLocation,
RazorResources.ParseError_TextTagCannotContainAttributes,
length: 4 /* text */);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any; Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
RecoverTextTag(); RecoverTextTag();
@ -751,6 +761,7 @@ namespace Microsoft.AspNet.Razor.Parser
Span.ChunkGenerator = SpanChunkGenerator.Null; Span.ChunkGenerator = SpanChunkGenerator.Null;
Accept(_bufferedOpenAngle); Accept(_bufferedOpenAngle);
var textLocation = CurrentLocation;
Assert(HtmlSymbolType.Text); Assert(HtmlSymbolType.Text);
AcceptAndMoveNext(); AcceptAndMoveNext();
@ -771,7 +782,10 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
Context.Source.Position = bookmark; Context.Source.Position = bookmark;
NextToken(); NextToken();
Context.OnError(tag.Item2, RazorResources.ParseError_TextTagCannotContainAttributes); Context.OnError(
textLocation,
RazorResources.ParseError_TextTagCannotContainAttributes,
length: 4 /* text */);
RecoverTextTag(); RecoverTextTag();
} }
@ -821,7 +835,10 @@ namespace Microsoft.AspNet.Razor.Parser
var seenClose = Optional(HtmlSymbolType.CloseAngle); var seenClose = Optional(HtmlSymbolType.CloseAngle);
if (!seenClose) if (!seenClose)
{ {
Context.OnError(tag.Item2, RazorResources.FormatParseError_UnfinishedTag(tag.Item1.Content)); Context.OnError(
SourceLocation.Advance(tag.Item2, "<"),
RazorResources.FormatParseError_UnfinishedTag(tag.Item1.Content),
Math.Max(tag.Item1.Content.Length, 1));
} }
else else
{ {
@ -883,7 +900,7 @@ namespace Microsoft.AspNet.Razor.Parser
Context.Source.Position = bookmark; Context.Source.Position = bookmark;
NextToken(); NextToken();
} }
else if (string.Equals(tagName, "script", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(tagName, ScriptTagName, StringComparison.OrdinalIgnoreCase))
{ {
CompleteTagBlockWithSpan(tagBlockWrapper, AcceptedCharacters.None, SpanKind.Markup); CompleteTagBlockWithSpan(tagBlockWrapper, AcceptedCharacters.None, SpanKind.Markup);
@ -916,7 +933,8 @@ namespace Microsoft.AspNet.Razor.Parser
var solidus = CurrentSymbol; var solidus = CurrentSymbol;
NextToken(); // Skip over '/', current should be text NextToken(); // Skip over '/', current should be text
if (At(HtmlSymbolType.Text) && string.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase)) if (At(HtmlSymbolType.Text) &&
string.Equals(CurrentSymbol.Content, ScriptTagName, StringComparison.OrdinalIgnoreCase))
{ {
seenEndScript = true; seenEndScript = true;
} }
@ -944,7 +962,10 @@ namespace Microsoft.AspNet.Razor.Parser
SkipToAndParseCode(HtmlSymbolType.CloseAngle); SkipToAndParseCode(HtmlSymbolType.CloseAngle);
if (!Optional(HtmlSymbolType.CloseAngle)) if (!Optional(HtmlSymbolType.CloseAngle))
{ {
Context.OnError(tagStart, RazorResources.FormatParseError_UnfinishedTag("script")); Context.OnError(
SourceLocation.Advance(tagStart, "</"),
RazorResources.FormatParseError_UnfinishedTag(ScriptTagName),
ScriptTagName.Length);
} }
Output(SpanKind.Markup); Output(SpanKind.Markup);
} }
@ -999,11 +1020,17 @@ namespace Microsoft.AspNet.Razor.Parser
} }
if (currentTag != null) if (currentTag != null)
{ {
Context.OnError(currentTag.Item2, RazorResources.FormatParseError_MissingEndTag(currentTag.Item1.Content)); Context.OnError(
SourceLocation.Advance(currentTag.Item2, "<"),
RazorResources.FormatParseError_MissingEndTag(currentTag.Item1.Content),
currentTag.Item1.Content.Length);
} }
else else
{ {
Context.OnError(tagStart, RazorResources.FormatParseError_UnexpectedEndTag(tagName)); Context.OnError(
SourceLocation.Advance(tagStart, "</"),
RazorResources.FormatParseError_UnexpectedEndTag(tagName),
tagName.Length);
} }
return false; return false;
} }
@ -1018,7 +1045,10 @@ namespace Microsoft.AspNet.Razor.Parser
tags.Pop(); tags.Pop();
} }
var tag = tags.Pop(); var tag = tags.Pop();
Context.OnError(tag.Item2, RazorResources.FormatParseError_MissingEndTag(tag.Item1.Content)); Context.OnError(
SourceLocation.Advance(tag.Item2, "<"),
RazorResources.FormatParseError_MissingEndTag(tag.Item1.Content),
tag.Item1.Content.Length);
} }
else if (complete) else if (complete)
{ {

View File

@ -200,14 +200,6 @@ namespace Microsoft.AspNet.Razor.Parser
_errorSink.OnError(error); _errorSink.OnError(error);
} }
public void OnError(SourceLocation location, string message)
{
EnusreNotTerminated();
AssertOnOwnerTask();
_errorSink.OnError(location, message);
}
public void OnError(SourceLocation location, string message, int length) public void OnError(SourceLocation location, string message, int length)
{ {
EnusreNotTerminated(); EnusreNotTerminated();
@ -216,12 +208,12 @@ namespace Microsoft.AspNet.Razor.Parser
_errorSink.OnError(location, message, length); _errorSink.OnError(location, message, length);
} }
public void OnError(SourceLocation location, string message, params object[] args) public void OnError(SourceLocation location, string message, int length, params object[] args)
{ {
EnusreNotTerminated(); EnusreNotTerminated();
AssertOnOwnerTask(); AssertOnOwnerTask();
OnError(location, string.Format(CultureInfo.CurrentCulture, message, args)); OnError(location, string.Format(CultureInfo.CurrentCulture, message, args), length);
} }
public ParserResults CompleteParse() public ParserResults CompleteParse()

View File

@ -296,8 +296,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
if (childSpan == null || childSpan.Kind != SpanKind.Markup) if (childSpan == null || childSpan.Kind != SpanKind.Markup)
{ {
errorSink.OnError(block.Children.First().Start, errorSink.OnError(
RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName)); block.Start,
RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName),
block.Length);
return null; return null;
} }
@ -316,7 +318,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
if (name == null) if (name == null)
{ {
errorSink.OnError(childSpan.Start, RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName)); errorSink.OnError(
childSpan.Start,
RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName),
childSpan.Length);
return null; return null;
} }

View File

@ -67,11 +67,12 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
chunkGenerator.RemoveTagHelperDescriptors ? chunkGenerator.RemoveTagHelperDescriptors ?
TagHelperDirectiveType.RemoveTagHelper : TagHelperDirectiveType.RemoveTagHelper :
TagHelperDirectiveType.AddTagHelper; TagHelperDirectiveType.AddTagHelper;
var textLocation = GetSubTextSourceLocation(span, chunkGenerator.LookupText);
var directiveDescriptor = new TagHelperDirectiveDescriptor var directiveDescriptor = new TagHelperDirectiveDescriptor
{ {
DirectiveText = chunkGenerator.LookupText, DirectiveText = chunkGenerator.LookupText,
Location = span.Start, Location = textLocation,
DirectiveType = directive DirectiveType = directive
}; };
@ -80,16 +81,26 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
else if (span.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator) else if (span.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator)
{ {
var chunkGenerator = (TagHelperPrefixDirectiveChunkGenerator)span.ChunkGenerator; var chunkGenerator = (TagHelperPrefixDirectiveChunkGenerator)span.ChunkGenerator;
var textLocation = GetSubTextSourceLocation(span, chunkGenerator.Prefix);
var directiveDescriptor = new TagHelperDirectiveDescriptor var directiveDescriptor = new TagHelperDirectiveDescriptor
{ {
DirectiveText = chunkGenerator.Prefix, DirectiveText = chunkGenerator.Prefix,
Location = span.Start, Location = textLocation,
DirectiveType = TagHelperDirectiveType.TagHelperPrefix DirectiveType = TagHelperDirectiveType.TagHelperPrefix
}; };
_directiveDescriptors.Add(directiveDescriptor); _directiveDescriptors.Add(directiveDescriptor);
} }
} }
private static SourceLocation GetSubTextSourceLocation(Span span, string text)
{
var startOffset = span.Content.IndexOf(text);
var offsetContent = span.Content.Substring(0, startOffset);
var offsetTextLocation = SourceLocation.Advance(span.Start, offsetContent);
return offsetTextLocation;
}
} }
} }

View File

@ -208,12 +208,12 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
{ {
// End tag TagHelper that states it shouldn't have an end tag. // End tag TagHelper that states it shouldn't have an end tag.
context.ErrorSink.OnError( context.ErrorSink.OnError(
tagBlock.Start, SourceLocation.Advance(tagBlock.Start, "</"),
RazorResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag( RazorResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag(
tagName, tagName,
invalidDescriptor.TypeName, invalidDescriptor.TypeName,
invalidDescriptor.TagStructure), invalidDescriptor.TagStructure),
tagBlock.Length); tagName.Length);
return false; return false;
} }
@ -233,8 +233,9 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
// Could not recover, the end tag helper has no corresponding start tag, create // Could not recover, the end tag helper has no corresponding start tag, create
// an error based on the current childBlock. // an error based on the current childBlock.
context.ErrorSink.OnError( context.ErrorSink.OnError(
tagBlock.Start, SourceLocation.Advance(tagBlock.Start, "</"),
RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(tagName)); RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(tagName),
tagName.Length);
return false; return false;
} }
@ -333,8 +334,9 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
tagName, tagName,
tracker.Builder.TagName, tracker.Builder.TagName,
allowedChildrenString); allowedChildrenString);
var errorStart = GetTagDeclarationErrorStart(tagBlock);
errorSink.OnError(tagBlock.Start, errorMessage, tagBlock.Length); errorSink.OnError(errorStart, errorMessage, tagName.Length);
} }
private static void ValidateDescriptors( private static void ValidateDescriptors(
@ -373,9 +375,12 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
// We assume an invalid syntax until we verify that the tag meets all of our "valid syntax" criteria. // We assume an invalid syntax until we verify that the tag meets all of our "valid syntax" criteria.
if (IsPartialTag(tag)) if (IsPartialTag(tag))
{ {
var errorStart = GetTagDeclarationErrorStart(tag);
context.ErrorSink.OnError( context.ErrorSink.OnError(
tag.Start, errorStart,
RazorResources.FormatTagHelpersParseTreeRewriter_MissingCloseAngle(tagName)); RazorResources.FormatTagHelpersParseTreeRewriter_MissingCloseAngle(tagName),
tagName.Length);
return false; return false;
} }
@ -383,6 +388,13 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
return true; return true;
} }
private static SourceLocation GetTagDeclarationErrorStart(Block tagBlock)
{
var advanceBy = IsEndTag(tagBlock) ? "</" : "<";
return SourceLocation.Advance(tagBlock.Start, advanceBy);
}
private static bool IsPartialTag(Block tagBlock) private static bool IsPartialTag(Block tagBlock)
{ {
// No need to validate the tag end because in order to be a tag block it must start with '<'. // No need to validate the tag end because in order to be a tag block it must start with '<'.
@ -501,9 +513,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
var malformedTagHelper = _trackerStack.Peek().Builder; var malformedTagHelper = _trackerStack.Peek().Builder;
context.ErrorSink.OnError( context.ErrorSink.OnError(
malformedTagHelper.Start, SourceLocation.Advance(malformedTagHelper.Start, "<"),
RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper( RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
malformedTagHelper.TagName)); malformedTagHelper.TagName),
malformedTagHelper.TagName.Length);
BuildCurrentlyTrackedTagHelperBlock(endTag: null); BuildCurrentlyTrackedTagHelperBlock(endTag: null);
} }

View File

@ -71,10 +71,12 @@ namespace Microsoft.AspNet.Razor.Parser
AcceptAndMoveNext(); AcceptAndMoveNext();
if (EndOfFile && ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure)) if (EndOfFile && ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure))
{ {
Context.OnError(start, Context.OnError(
RazorResources.FormatParseError_Expected_CloseBracket_Before_EOF( start,
Language.GetSample(left), RazorResources.FormatParseError_Expected_CloseBracket_Before_EOF(
Language.GetSample(right))); Language.GetSample(left),
Language.GetSample(right)),
length: 1 /* { OR } */);
} }
return Balance(mode, left, right, start); return Balance(mode, left, right, start);
@ -119,10 +121,12 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
if ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure) if ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure)
{ {
Context.OnError(start, Context.OnError(
RazorResources.FormatParseError_Expected_CloseBracket_Before_EOF( start,
Language.GetSample(left), RazorResources.FormatParseError_Expected_CloseBracket_Before_EOF(
Language.GetSample(right))); Language.GetSample(left),
Language.GetSample(right)),
length: 1 /* { OR } */);
} }
if ((mode & BalancingModes.BacktrackOnFailure) == BalancingModes.BacktrackOnFailure) if ((mode & BalancingModes.BacktrackOnFailure) == BalancingModes.BacktrackOnFailure)
{ {
@ -355,9 +359,17 @@ namespace Microsoft.AspNet.Razor.Parser
error = RazorResources.FormatErrorComponent_Character(CurrentSymbol.Content); error = RazorResources.FormatErrorComponent_Character(CurrentSymbol.Content);
} }
Context.OnError( int errorLength;
CurrentLocation, if (CurrentSymbol == null || CurrentSymbol.Content == null)
errorBase(error)); {
errorLength = 1;
}
else
{
errorLength = Math.Max(CurrentSymbol.Content.Length, 1);
}
Context.OnError(CurrentLocation, errorBase(error), errorLength);
} }
return found; return found;
} }
@ -521,7 +533,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!Optional(KnownSymbolType.CommentStar)) if (!Optional(KnownSymbolType.CommentStar))
{ {
errorReported = true; errorReported = true;
Context.OnError(start, RazorResources.ParseError_RazorComment_Not_Terminated); Context.OnError(
start,
RazorResources.ParseError_RazorComment_Not_Terminated,
length: 2 /* @* */);
} }
else else
{ {
@ -533,7 +548,10 @@ namespace Microsoft.AspNet.Razor.Parser
if (!errorReported) if (!errorReported)
{ {
errorReported = true; errorReported = true;
Context.OnError(start, RazorResources.ParseError_RazorComment_Not_Terminated); Context.OnError(
start,
RazorResources.ParseError_RazorComment_Not_Terminated,
length: 2 /* @* */);
} }
} }
else else

View File

@ -11,18 +11,16 @@ namespace Microsoft.AspNet.Razor
{ {
internal const int DefaultErrorLength = 1; internal const int DefaultErrorLength = 1;
/// <summary>
/// Used only for deserialization.
/// </summary>
public RazorError() public RazorError()
: this(message: string.Empty, location: SourceLocation.Undefined) : this(message: string.Empty, location: SourceLocation.Undefined, length: -1)
{ {
} }
public RazorError(string message, SourceLocation location) public RazorError(string message, int absoluteIndex, int lineIndex, int columnIndex, int length)
: this(message, location, DefaultErrorLength) : this(message, new SourceLocation(absoluteIndex, lineIndex, columnIndex), length)
{
}
public RazorError(string message, int absoluteIndex, int lineIndex, int columnIndex)
: this(message, new SourceLocation(absoluteIndex, lineIndex, columnIndex))
{ {
} }
@ -33,11 +31,6 @@ namespace Microsoft.AspNet.Razor
Length = length; Length = length;
} }
public RazorError(string message, int absoluteIndex, int lineIndex, int columnIndex, int length)
: this(message, new SourceLocation(absoluteIndex, lineIndex, columnIndex), length)
{
}
/// <summary> /// <summary>
/// Gets (or sets) the message describing the error. /// Gets (or sets) the message describing the error.
/// </summary> /// </summary>

View File

@ -264,7 +264,11 @@ namespace Microsoft.AspNet.Razor.Tokenizer
} }
else if (EndOfFile) else if (EndOfFile)
{ {
CurrentErrors.Add(new RazorError(RazorResources.ParseError_Unterminated_String_Literal, CurrentStart)); CurrentErrors.Add(
new RazorError(
RazorResources.ParseError_Unterminated_String_Literal,
CurrentStart,
length: 1 /* end of file */));
} }
return Transition(EndSymbol(CSharpSymbolType.StringLiteral), Data); return Transition(EndSymbol(CSharpSymbolType.StringLiteral), Data);
} }
@ -285,7 +289,11 @@ namespace Microsoft.AspNet.Razor.Tokenizer
} }
else if (EndOfFile || ParserHelpers.IsNewLine(CurrentCharacter)) else if (EndOfFile || ParserHelpers.IsNewLine(CurrentCharacter))
{ {
CurrentErrors.Add(new RazorError(RazorResources.ParseError_Unterminated_String_Literal, CurrentStart)); CurrentErrors.Add(
new RazorError(
RazorResources.ParseError_Unterminated_String_Literal,
CurrentStart,
length: 1 /* " */));
} }
else else
{ {
@ -300,7 +308,11 @@ namespace Microsoft.AspNet.Razor.Tokenizer
TakeUntil(c => c == '*'); TakeUntil(c => c == '*');
if (EndOfFile) if (EndOfFile)
{ {
CurrentErrors.Add(new RazorError(RazorResources.ParseError_BlockComment_Not_Terminated, CurrentStart)); CurrentErrors.Add(
new RazorError(
RazorResources.ParseError_BlockComment_Not_Terminated,
CurrentStart,
length: 1 /* end of file */));
return Transition(EndSymbol(CSharpSymbolType.Comment), Data); return Transition(EndSymbol(CSharpSymbolType.Comment), Data);
} }
if (CurrentCharacter == '*') if (CurrentCharacter == '*')