Make namespace tokens tolerant to EOF and invalid states.

- Updated our `QualifiedIdentifier` to properly parse invalid namespaces. This is more consistent with how the rest of the system works; we consume tokens until we determine if we're in an invalid or valid state. If invalid, we log an error and put back all invalid tokens for the parser to treat as non-directive tokens.
- Added unit tests to validate our extensible directive system can handle malformed namespace tokens at EOF and inline.
- Added a code gen test for Razor.Extensions to prove we've fixed the underlying issue for our `@namespace` directive that crashed VS.

#1393
This commit is contained in:
N. Taylor Mullen 2017-05-31 12:17:37 -07:00
parent 4c98b7f8f3
commit 2e8c154fcb
9 changed files with 280 additions and 8 deletions

View File

@ -909,21 +909,53 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// qualified-identifier:
// identifier
// qualified-identifier . identifier
protected bool QualifiedIdentifier()
protected bool QualifiedIdentifier(out int identifierLength)
{
if (At(CSharpSymbolType.Identifier))
var currentIdentifierLength = 0;
var expectingDot = false;
var tokens = ReadWhile(token =>
{
AcceptAndMoveNext();
if (Optional(CSharpSymbolType.Dot))
var type = token.Type;
if ((expectingDot && type == CSharpSymbolType.Dot) ||
(!expectingDot && type == CSharpSymbolType.Identifier))
{
return QualifiedIdentifier();
expectingDot = !expectingDot;
return true;
}
if (type != CSharpSymbolType.WhiteSpace &&
type != CSharpSymbolType.NewLine)
{
expectingDot = false;
currentIdentifierLength += token.Content.Length;
}
return false;
});
identifierLength = currentIdentifierLength;
var validQualifiedIdentifier = expectingDot;
if (validQualifiedIdentifier)
{
foreach (var token in tokens)
{
identifierLength += token.Content.Length;
Accept(token);
}
return true;
}
else
{
PutCurrentBack();
foreach (var token in tokens)
{
identifierLength += token.Content.Length;
PutBack(token);
}
EnsureCurrent();
return false;
}
}
@ -1574,12 +1606,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
break;
case DirectiveTokenKind.Namespace:
if (!QualifiedIdentifier())
if (!QualifiedIdentifier(out var identifierLength))
{
Context.ErrorSink.OnError(
CurrentStart,
LegacyResources.FormatDirectiveExpectsNamespace(descriptor.Name),
CurrentSymbol.Content.Length);
identifierLength);
return;
}

View File

@ -27,6 +27,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.IntegrationTests
private static readonly RazorSourceDocument DefaultImports = MvcRazorTemplateEngine.GetDefaultImports();
#region Runtime
[Fact]
public void InvalidNamespaceAtEOF_Runtime()
{
var references = CreateCompilationReferences(CurrentMvcShim);
RunRuntimeTest(references, expectedErrors: new[]
{
"Identifier expected"
});
}
[Fact]
public void IncompleteDirectives_Runtime()
{
@ -235,6 +245,17 @@ public class DivTagHelper : {typeof(TagHelper).FullName}
#endregion
#region DesignTime
[Fact]
public void InvalidNamespaceAtEOF_DesignTime()
{
var references = CreateCompilationReferences(CurrentMvcShim);
RunDesignTimeTest(references,
expectedErrors: new[]
{
"Identifier expected"
});
}
[Fact]
public void IncompleteDirectives_DesignTime()
{

View File

@ -0,0 +1,35 @@
namespace
{
#line hidden
using TModel = global::System.Object;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InvalidNamespaceAtEOF_cshtml : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
{
#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
}
#pragma warning restore 219
private static System.Object __o = null;
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
}
}

View File

@ -0,0 +1,36 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - - TModel = global::System.Object
UsingStatement - (1:0,1 [12] ) - System
UsingStatement - (16:1,1 [32] ) - System.Collections.Generic
UsingStatement - (51:2,1 [17] ) - System.Linq
UsingStatement - (71:3,1 [28] ) - System.Threading.Tasks
UsingStatement - (102:4,1 [30] ) - Microsoft.AspNetCore.Mvc
UsingStatement - (135:5,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingStatement - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InvalidNamespaceAtEOF_cshtml - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
DesignTimeDirective -
DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel>
DirectiveToken - (294:7,71 [4] ) - Html
DirectiveToken - (308:8,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper
DirectiveToken - (363:8,63 [4] ) - Json
DirectiveToken - (377:9,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper
DirectiveToken - (431:9,62 [9] ) - Component
DirectiveToken - (450:10,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper
DirectiveToken - (494:10,52 [3] ) - Url
DirectiveToken - (507:11,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider
DirectiveToken - (578:11,79 [23] ) - ModelExpressionProvider
DirectiveToken - (617:12,14 [96] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (729:13,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (832:14,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor
CSharpStatement -
RazorIRToken - - CSharp - private static System.Object __o = null;
MethodDeclaration - - public - async, override - global::System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (11:0,11 [5] InvalidNamespaceAtEOF.cshtml)
RazorIRToken - (11:0,11 [5] InvalidNamespaceAtEOF.cshtml) - Html - Test.
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -

View File

@ -0,0 +1,33 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/InvalidNamespaceAtEOF.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "de132bd3e2a46a0d2ec953a168427c01e5829cde"
namespace
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InvalidNamespaceAtEOF_cshtml : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
BeginContext(11, 5, true);
WriteLiteral("Test.");
EndContext();
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
}
}

View File

@ -0,0 +1,23 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - (1:0,1 [14] ) - System
UsingStatement - (16:1,1 [34] ) - System.Collections.Generic
UsingStatement - (51:2,1 [19] ) - System.Linq
UsingStatement - (71:3,1 [30] ) - System.Threading.Tasks
UsingStatement - (102:4,1 [32] ) - Microsoft.AspNetCore.Mvc
UsingStatement - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingStatement - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_InvalidNamespaceAtEOF_cshtml - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
MethodDeclaration - - public - async, override - global::System.Threading.Tasks.Task - ExecuteAsync
CSharpStatement -
RazorIRToken - - CSharp - BeginContext(11, 5, true);
HtmlContent - (11:0,11 [5] InvalidNamespaceAtEOF.cshtml)
RazorIRToken - (11:0,11 [5] InvalidNamespaceAtEOF.cshtml) - Html - Test.
CSharpStatement -
RazorIRToken - - CSharp - EndContext();
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -

View File

@ -10,6 +10,97 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpDirectivesTest : CsHtmlCodeParserTestBase
{
[Fact]
public void DirectiveDescriptor_CanHandleEOFIncompleteNamespaceTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddNamespaceToken());
// Act & Assert
ParseCodeBlockTest(
"@custom System.",
new[] { descriptor },
new DirectiveBlock(
new DirectiveChunkGenerator(descriptor),
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace)),
new RazorError(
LegacyResources.FormatDirectiveExpectsNamespace("custom"),
8, 0, 8, 7));
}
[Fact]
public void DirectiveDescriptor_CanHandleEOFInvalidNamespaceTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddNamespaceToken());
// Act & Assert
ParseCodeBlockTest(
"@custom System<",
new[] { descriptor },
new DirectiveBlock(
new DirectiveChunkGenerator(descriptor),
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace)),
new RazorError(
LegacyResources.FormatDirectiveExpectsNamespace("custom"),
8, 0, 8, 7));
}
[Fact]
public void DirectiveDescriptor_CanHandleIncompleteNamespaceTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddNamespaceToken());
// Act & Assert
ParseCodeBlockTest(
"@custom System." + Environment.NewLine,
new[] { descriptor },
new DirectiveBlock(
new DirectiveChunkGenerator(descriptor),
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace)),
new RazorError(
LegacyResources.FormatDirectiveExpectsNamespace("custom"),
8, 0, 8, 7));
}
[Fact]
public void DirectiveDescriptor_CanHandleInvalidNamespaceTokens()
{
// Arrange
var descriptor = DirectiveDescriptor.CreateDirective(
"custom",
DirectiveKind.SingleLine,
b => b.AddNamespaceToken());
// Act & Assert
ParseCodeBlockTest(
"@custom System<" + Environment.NewLine,
new[] { descriptor },
new DirectiveBlock(
new DirectiveChunkGenerator(descriptor),
Factory.CodeTransition(),
Factory.MetaCode("custom").Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Code, " ", markup: false).Accepts(AcceptedCharacters.WhiteSpace)),
new RazorError(
LegacyResources.FormatDirectiveExpectsNamespace("custom"),
8, 0, 8, 7));
}
[Fact]
public void DirectiveDescriptor_UnderstandsTypeTokens()
{