328 lines
12 KiB
C#
328 lines
12 KiB
C#
// 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 System;
|
|
using System.Diagnostics;
|
|
using Microsoft.AspNet.Razor.Chunks.Generators;
|
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
|
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
|
|
|
namespace Microsoft.AspNet.Razor.Parser
|
|
{
|
|
public partial class CSharpCodeParser
|
|
{
|
|
private void SetupDirectives()
|
|
{
|
|
MapDirectives(TagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword);
|
|
MapDirectives(AddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword);
|
|
MapDirectives(RemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword);
|
|
MapDirectives(InheritsDirective, SyntaxConstants.CSharp.InheritsKeyword);
|
|
MapDirectives(FunctionsDirective, SyntaxConstants.CSharp.FunctionsKeyword);
|
|
MapDirectives(SectionDirective, SyntaxConstants.CSharp.SectionKeyword);
|
|
}
|
|
|
|
protected virtual void TagHelperPrefixDirective()
|
|
{
|
|
TagHelperDirective(
|
|
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
|
|
new TagHelperPrefixDirectiveChunkGenerator());
|
|
}
|
|
|
|
protected virtual void AddTagHelperDirective()
|
|
{
|
|
TagHelperDirective(SyntaxConstants.CSharp.AddTagHelperKeyword, new AddTagHelperChunkGenerator());
|
|
}
|
|
|
|
protected virtual void RemoveTagHelperDirective()
|
|
{
|
|
TagHelperDirective(SyntaxConstants.CSharp.RemoveTagHelperKeyword, new RemoveTagHelperChunkGenerator());
|
|
}
|
|
|
|
protected virtual void SectionDirective()
|
|
{
|
|
var nested = Context.IsWithin(BlockType.Section);
|
|
var errorReported = false;
|
|
|
|
// Set the block and span type
|
|
Context.CurrentBlock.Type = BlockType.Section;
|
|
|
|
// Verify we're on "section" and accept
|
|
AssertDirective(SyntaxConstants.CSharp.SectionKeyword);
|
|
var startLocation = CurrentLocation;
|
|
AcceptAndMoveNext();
|
|
|
|
if (nested)
|
|
{
|
|
Context.OnError(
|
|
startLocation,
|
|
RazorResources.FormatParseError_Sections_Cannot_Be_Nested(RazorResources.SectionExample_CS),
|
|
Span.GetContent().Value.Length);
|
|
errorReported = true;
|
|
}
|
|
|
|
var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
|
|
|
|
// Get the section name
|
|
var sectionName = string.Empty;
|
|
if (!Required(CSharpSymbolType.Identifier,
|
|
errorIfNotFound: true,
|
|
errorBase: RazorResources.FormatParseError_Unexpected_Character_At_Section_Name_Start))
|
|
{
|
|
if (!errorReported)
|
|
{
|
|
errorReported = true;
|
|
}
|
|
|
|
PutCurrentBack();
|
|
PutBack(whitespace);
|
|
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: false));
|
|
}
|
|
else
|
|
{
|
|
Accept(whitespace);
|
|
sectionName = CurrentSymbol.Content;
|
|
AcceptAndMoveNext();
|
|
}
|
|
Context.CurrentBlock.ChunkGenerator = new SectionChunkGenerator(sectionName);
|
|
|
|
var errorLocation = CurrentLocation;
|
|
whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
|
|
|
|
// Get the starting brace
|
|
var sawStartingBrace = At(CSharpSymbolType.LeftBrace);
|
|
if (!sawStartingBrace)
|
|
{
|
|
if (!errorReported)
|
|
{
|
|
errorReported = true;
|
|
Context.OnError(
|
|
errorLocation,
|
|
RazorResources.ParseError_MissingOpenBraceAfterSection,
|
|
length: 1 /* { */);
|
|
}
|
|
|
|
PutCurrentBack();
|
|
PutBack(whitespace);
|
|
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: false));
|
|
Optional(CSharpSymbolType.NewLine);
|
|
Output(SpanKind.MetaCode);
|
|
CompleteBlock();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Accept(whitespace);
|
|
}
|
|
|
|
// Set up edit handler
|
|
var editHandler = new AutoCompleteEditHandler(Language.TokenizeString, autoCompleteAtEndOfSpan: true);
|
|
|
|
Span.EditHandler = editHandler;
|
|
Span.Accept(CurrentSymbol);
|
|
|
|
// Output Metacode then switch to section parser
|
|
Output(SpanKind.MetaCode);
|
|
SectionBlock("{", "}", caseSensitive: true);
|
|
|
|
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
|
// Check for the terminating "}"
|
|
if (!Optional(CSharpSymbolType.RightBrace))
|
|
{
|
|
editHandler.AutoCompleteString = "}";
|
|
Context.OnError(
|
|
CurrentLocation,
|
|
RazorResources.FormatParseError_Expected_X(Language.GetSample(CSharpSymbolType.RightBrace)),
|
|
length: 1 /* } */);
|
|
}
|
|
else
|
|
{
|
|
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
|
|
}
|
|
CompleteBlock(insertMarkerIfNecessary: false, captureWhitespaceToEndOfLine: true);
|
|
Output(SpanKind.MetaCode);
|
|
return;
|
|
}
|
|
|
|
protected virtual void FunctionsDirective()
|
|
{
|
|
// Set the block type
|
|
Context.CurrentBlock.Type = BlockType.Functions;
|
|
|
|
// Verify we're on "functions" and accept
|
|
AssertDirective(SyntaxConstants.CSharp.FunctionsKeyword);
|
|
var block = new Block(CurrentSymbol);
|
|
AcceptAndMoveNext();
|
|
|
|
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
|
|
|
|
if (!At(CSharpSymbolType.LeftBrace))
|
|
{
|
|
Context.OnError(
|
|
CurrentLocation,
|
|
RazorResources.FormatParseError_Expected_X(Language.GetSample(CSharpSymbolType.LeftBrace)),
|
|
length: 1 /* { */);
|
|
CompleteBlock();
|
|
Output(SpanKind.MetaCode);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
|
|
}
|
|
|
|
// Capture start point and continue
|
|
var blockStart = CurrentLocation;
|
|
AcceptAndMoveNext();
|
|
|
|
// Output what we've seen and continue
|
|
Output(SpanKind.MetaCode);
|
|
|
|
var editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
|
|
Span.EditHandler = editHandler;
|
|
|
|
Balance(BalancingModes.NoErrorOnFailure, CSharpSymbolType.LeftBrace, CSharpSymbolType.RightBrace, blockStart);
|
|
Span.ChunkGenerator = new TypeMemberChunkGenerator();
|
|
if (!At(CSharpSymbolType.RightBrace))
|
|
{
|
|
editHandler.AutoCompleteString = "}";
|
|
Context.OnError(
|
|
blockStart,
|
|
RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(block.Name, "}", "{"),
|
|
length: 1 /* } */);
|
|
CompleteBlock();
|
|
Output(SpanKind.Code);
|
|
}
|
|
else
|
|
{
|
|
Output(SpanKind.Code);
|
|
Assert(CSharpSymbolType.RightBrace);
|
|
Span.ChunkGenerator = SpanChunkGenerator.Null;
|
|
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
|
|
AcceptAndMoveNext();
|
|
CompleteBlock();
|
|
Output(SpanKind.MetaCode);
|
|
}
|
|
}
|
|
|
|
protected virtual void InheritsDirective()
|
|
{
|
|
// Verify we're on the right keyword and accept
|
|
AssertDirective(SyntaxConstants.CSharp.InheritsKeyword);
|
|
AcceptAndMoveNext();
|
|
|
|
InheritsDirectiveCore();
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
protected void AssertDirective(string directive)
|
|
{
|
|
Assert(CSharpSymbolType.Identifier);
|
|
Debug.Assert(string.Equals(CurrentSymbol.Content, directive, StringComparison.Ordinal));
|
|
}
|
|
|
|
protected void InheritsDirectiveCore()
|
|
{
|
|
BaseTypeDirective(
|
|
RazorResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName,
|
|
baseType => new SetBaseTypeChunkGenerator(baseType));
|
|
}
|
|
|
|
protected void BaseTypeDirective(string noTypeNameError, Func<string, SpanChunkGenerator> createChunkGenerator)
|
|
{
|
|
var keywordStartLocation = Span.Start;
|
|
|
|
// Set the block type
|
|
Context.CurrentBlock.Type = BlockType.Directive;
|
|
|
|
var keywordLength = Span.GetContent().Value.Length;
|
|
|
|
// Accept whitespace
|
|
var remainingWhitespace = AcceptSingleWhiteSpaceCharacter();
|
|
|
|
if (Span.Symbols.Count > 1)
|
|
{
|
|
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
|
|
}
|
|
|
|
Output(SpanKind.MetaCode);
|
|
|
|
if (remainingWhitespace != null)
|
|
{
|
|
Accept(remainingWhitespace);
|
|
}
|
|
|
|
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
|
|
|
|
if (EndOfFile || At(CSharpSymbolType.WhiteSpace) || At(CSharpSymbolType.NewLine))
|
|
{
|
|
Context.OnError(
|
|
keywordStartLocation,
|
|
noTypeNameError,
|
|
keywordLength);
|
|
}
|
|
|
|
// Parse to the end of the line
|
|
AcceptUntil(CSharpSymbolType.NewLine);
|
|
if (!Context.DesignTimeMode)
|
|
{
|
|
// We want the newline to be treated as code, but it causes issues at design-time.
|
|
Optional(CSharpSymbolType.NewLine);
|
|
}
|
|
|
|
// Pull out the type name
|
|
string baseType = Span.GetContent();
|
|
|
|
// Set up chunk generation
|
|
Span.ChunkGenerator = createChunkGenerator(baseType.Trim());
|
|
|
|
// Output the span and finish the block
|
|
CompleteBlock();
|
|
Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline);
|
|
}
|
|
|
|
private void TagHelperDirective(string keyword, ISpanChunkGenerator chunkGenerator)
|
|
{
|
|
AssertDirective(keyword);
|
|
var keywordStartLocation = CurrentLocation;
|
|
|
|
// Accept the directive name
|
|
AcceptAndMoveNext();
|
|
|
|
// Set the block type
|
|
Context.CurrentBlock.Type = BlockType.Directive;
|
|
|
|
var keywordLength = Span.GetContent().Value.Length;
|
|
|
|
var foundWhitespace = At(CSharpSymbolType.WhiteSpace);
|
|
AcceptWhile(CSharpSymbolType.WhiteSpace);
|
|
|
|
// If we found whitespace then any content placed within the whitespace MAY cause a destructive change
|
|
// to the document. We can't accept it.
|
|
Output(SpanKind.MetaCode, foundWhitespace ? AcceptedCharacters.None : AcceptedCharacters.AnyExceptNewline);
|
|
|
|
if (EndOfFile || At(CSharpSymbolType.NewLine))
|
|
{
|
|
Context.OnError(
|
|
keywordStartLocation,
|
|
RazorResources.FormatParseError_DirectiveMustHaveValue(keyword),
|
|
keywordLength);
|
|
}
|
|
else
|
|
{
|
|
// Need to grab the current location before we accept until the end of the line.
|
|
var startLocation = CurrentLocation;
|
|
|
|
// Parse to the end of the line. Essentially accepts anything until end of line, comments, invalid code
|
|
// etc.
|
|
AcceptUntil(CSharpSymbolType.NewLine);
|
|
}
|
|
|
|
Span.ChunkGenerator = chunkGenerator;
|
|
|
|
// Output the span and finish the block
|
|
CompleteBlock();
|
|
Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline);
|
|
}
|
|
}
|
|
}
|