diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpCodeParser.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpCodeParser.cs index e0fc7790c1..a6e048f189 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpCodeParser.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpCodeParser.cs @@ -482,7 +482,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy // Set up auto-complete and parse the code block var editHandler = new AutoCompleteEditHandler(Language.TokenizeString); SpanContext.EditHandler = editHandler; - ParseCodeBlock(builder, block, acceptTerminatingBrace: false); + ParseCodeBlock(builder, block); + + if (EndOfFile) + { + Context.ErrorSink.OnError( + RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF( + new SourceSpan(block.Start, contentLength: 1 /* { OR } */), block.Name, "}", "{")); + } EnsureCurrent(); SpanContext.ChunkGenerator = new StatementChunkGenerator(); @@ -521,7 +528,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy return SyntaxFactory.CSharpStatementBody(leftBrace, codeBlock, rightBrace); } - private void ParseCodeBlock(in SyntaxListBuilder builder, Block block, bool acceptTerminatingBrace = true) + private void ParseCodeBlock(in SyntaxListBuilder builder, Block block) { EnsureCurrent(); while (!EndOfFile && !At(SyntaxKind.RightBrace)) @@ -530,19 +537,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy ParseStatement(builder, block: block); EnsureCurrent(); } - - if (EndOfFile) - { - Context.ErrorSink.OnError( - RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF( - new SourceSpan(block.Start, contentLength: 1 /* { OR } */), block.Name, "}", "{")); - } - else if (acceptTerminatingBrace) - { - Assert(SyntaxKind.RightBrace); - SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; - AcceptAndMoveNext(); - } } private void ParseStatement(in SyntaxListBuilder builder, Block block) @@ -634,6 +628,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy // Verbatim Block AcceptAndMoveNext(); ParseCodeBlock(builder, block); + + // ParseCodeBlock is responsible for parsing the insides of a code block (non-inclusive of braces). + // Therefore, there's one of two cases after parsing: + // 1. We've hit the End of File (incomplete parse block). + // 2. It's a complete parse block and we're at a right brace. + + if (EndOfFile) + { + Context.ErrorSink.OnError( + RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF( + new SourceSpan(block.Start, contentLength: 1 /* { OR } */), block.Name, "}", "{")); + } + else + { + Assert(SyntaxKind.RightBrace); + SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None; + AcceptAndMoveNext(); + } break; case SyntaxKind.Keyword: if (!TryParseKeyword(builder, whitespace: null, transition: null)) @@ -736,7 +748,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy token.Kind != SyntaxKind.LeftBracket && token.Kind != SyntaxKind.RightBrace); - if (At(SyntaxKind.LeftBrace) || + if ((!Context.FeatureFlags.AllowRazorInAllCodeBlocks && At(SyntaxKind.LeftBrace)) || At(SyntaxKind.LeftParenthesis) || At(SyntaxKind.LeftBracket)) { @@ -752,6 +764,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy return; } } + else if (Context.FeatureFlags.AllowRazorInAllCodeBlocks && At(SyntaxKind.LeftBrace)) + { + Accept(read); + return; + } else if (At(SyntaxKind.Transition) && (NextIs(SyntaxKind.LessThan, SyntaxKind.Colon))) { Accept(read); @@ -847,6 +864,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy MapDirectives(ParseTagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword); MapDirectives(ParseAddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword); MapDirectives(ParseRemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword); + + // If there wasn't any extensible directives relating to the reserved directives then map them. + if (!_directiveParserMap.ContainsKey("class")) + { + MapDirectives(ParseReservedDirective, "class"); + } + + if (!_directiveParserMap.ContainsKey("namespace")) + { + MapDirectives(ParseReservedDirective, "namespace"); + } } private void EnsureDirectiveIsAtStartOfLine() @@ -883,17 +911,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy }); Keywords.Add(directive); - - // These C# keywords are reserved for use in directives. It's an error to use them outside of - // a directive. This code removes the error generation if the directive *is* registered. - if (string.Equals(directive, "class", StringComparison.OrdinalIgnoreCase)) - { - _keywordParserMap.Remove(CSharpKeyword.Class); - } - else if (string.Equals(directive, "namespace", StringComparison.OrdinalIgnoreCase)) - { - _keywordParserMap.Remove(CSharpKeyword.Namespace); - } } } @@ -1409,11 +1426,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy ParseDirectiveBlock(directiveBuilder, descriptor, parseChildren: (childBuilder, startingBraceLocation) => { NextToken(); - Balance(childBuilder, BalancingModes.NoErrorOnFailure, SyntaxKind.LeftBrace, SyntaxKind.RightBrace, startingBraceLocation); - SpanContext.ChunkGenerator = new StatementChunkGenerator(); + var existingEditHandler = SpanContext.EditHandler; SpanContext.EditHandler = new CodeBlockEditHandler(Language.TokenizeString); + if (Context.FeatureFlags.AllowRazorInAllCodeBlocks) + { + var block = new Block(descriptor.Directive, directiveStart); + ParseCodeBlock(childBuilder, block); + } + else + { + Balance(childBuilder, BalancingModes.NoErrorOnFailure, SyntaxKind.LeftBrace, SyntaxKind.RightBrace, startingBraceLocation); + } + + SpanContext.ChunkGenerator = new StatementChunkGenerator(); + AcceptMarkerTokenIfNecessary(); childBuilder.Add(OutputTokensAsStatementLiteral()); @@ -1607,7 +1635,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy MapKeywords(ParseTryStatement, CSharpKeyword.Try); MapKeywords(ParseDoStatement, CSharpKeyword.Do); MapKeywords(ParseUsingKeyword, CSharpKeyword.Using); - MapKeywords(ParseReservedDirective, CSharpKeyword.Class, CSharpKeyword.Namespace); } private void MapExpressionKeyword(Action, CSharpTransitionSyntax> handler, CSharpKeyword keyword) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CodeBlockEditHandler.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CodeBlockEditHandler.cs index 8ecbd85906..db4f7920ab 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CodeBlockEditHandler.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CodeBlockEditHandler.cs @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { var relativePosition = change.Span.AbsoluteIndex - target.Position; - if (target.GetContent().IndexOfAny(new[] { '{', '}' }, relativePosition, change.Span.Length) >= 0) + if (target.GetContent().IndexOfAny(new[] { '{', '}', '@', '<', '*', }, relativePosition, change.Span.Length) >= 0) { return true; } @@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy // Internal for testing internal static bool ContainsInvalidContent(SourceChange change) { - if (change.NewText.IndexOfAny(new[] { '{', '}' }) >= 0) + if (change.NewText.IndexOfAny(new[] { '{', '}', '@', '<', '*', }) >= 0) { return true; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorParserFeatureFlags.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorParserFeatureFlags.cs index e70f892384..22a1d731df 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorParserFeatureFlags.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorParserFeatureFlags.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language var allowMinimizedBooleanTagHelperAttributes = false; var allowHtmlCommentsInTagHelpers = false; var allowComponentFileKind = false; + var allowRazorInAllCodeBlocks = false; var experimental_AllowConditionalDataDashAttributes = false; if (version.CompareTo(RazorLanguageVersion.Version_2_1) >= 0) @@ -23,6 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language { // Added in 3.0 allowComponentFileKind = true; + allowRazorInAllCodeBlocks = true; } if (version.CompareTo(RazorLanguageVersion.Experimental) >= 0) @@ -34,6 +36,7 @@ namespace Microsoft.AspNetCore.Razor.Language allowMinimizedBooleanTagHelperAttributes, allowHtmlCommentsInTagHelpers, allowComponentFileKind, + allowRazorInAllCodeBlocks, experimental_AllowConditionalDataDashAttributes); } @@ -43,6 +46,8 @@ namespace Microsoft.AspNetCore.Razor.Language public abstract bool AllowComponentFileKind { get; } + public abstract bool AllowRazorInAllCodeBlocks { get; } + public abstract bool EXPERIMENTAL_AllowConditionalDataDashAttributes { get; } private class DefaultRazorParserFeatureFlags : RazorParserFeatureFlags @@ -51,11 +56,13 @@ namespace Microsoft.AspNetCore.Razor.Language bool allowMinimizedBooleanTagHelperAttributes, bool allowHtmlCommentsInTagHelpers, bool allowComponentFileKind, + bool allowRazorInAllCodeBlocks, bool experimental_AllowConditionalDataDashAttributes) { AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes; AllowHtmlCommentsInTagHelpers = allowHtmlCommentsInTagHelpers; AllowComponentFileKind = allowComponentFileKind; + AllowRazorInAllCodeBlocks = allowRazorInAllCodeBlocks; EXPERIMENTAL_AllowConditionalDataDashAttributes = experimental_AllowConditionalDataDashAttributes; } @@ -65,6 +72,8 @@ namespace Microsoft.AspNetCore.Razor.Language public override bool AllowComponentFileKind { get; } + public override bool AllowRazorInAllCodeBlocks { get; } + public override bool EXPERIMENTAL_AllowConditionalDataDashAttributes { get; } } }