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:
parent
4c98b7f8f3
commit
2e8c154fcb
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
@namespace Test.
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 -
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 -
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue