aspnetcore/src/Microsoft.AspNet.Mvc.Razor..../MvcRazorCodeParser.cs

171 lines
6.3 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.Diagnostics;
using Microsoft.AspNet.Mvc.Razor.Host;
using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Chunks.Generators;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcRazorCodeParser : CSharpCodeParser
{
private const string ModelKeyword = "model";
private const string InjectKeyword = "inject";
private readonly string _baseType;
private SourceLocation? _endInheritsLocation;
private bool _modelStatementFound;
public MvcRazorCodeParser(string baseType)
{
_baseType = baseType;
MapDirectives(ModelDirective, ModelKeyword);
MapDirectives(InjectDirective, InjectKeyword);
}
protected override void InheritsDirective()
{
// Verify we're on the right keyword and accept
AssertDirective(SyntaxConstants.CSharp.InheritsKeyword);
AcceptAndMoveNext();
_endInheritsLocation = CurrentLocation;
InheritsDirectiveCore();
CheckForInheritsAndModelStatements();
}
private void CheckForInheritsAndModelStatements()
{
if (_modelStatementFound && _endInheritsLocation.HasValue)
{
Context.OnError(_endInheritsLocation.Value,
Resources.FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(ModelKeyword));
}
}
protected virtual void ModelDirective()
{
// Verify we're on the right keyword and accept
AssertDirective(ModelKeyword);
var startModelLocation = CurrentLocation;
AcceptAndMoveNext();
BaseTypeDirective(Resources.FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(ModelKeyword),
CreateModelChunkGenerator);
if (_modelStatementFound)
{
Context.OnError(startModelLocation,
Resources.FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(ModelKeyword),
ModelKeyword.Length);
}
_modelStatementFound = true;
CheckForInheritsAndModelStatements();
}
protected virtual void InjectDirective()
{
// @inject MyApp.MyService MyServicePropertyName
AssertDirective(InjectKeyword);
var startLocation = CurrentLocation;
AcceptAndMoveNext();
Context.CurrentBlock.Type = BlockType.Directive;
// Accept whitespace
var remainingWhitespace = AcceptSingleWhiteSpaceCharacter();
var keywordwithSingleWhitespaceLength = Span.GetContent().Value.Length;
if (Span.Symbols.Count > 1)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
Output(SpanKind.MetaCode);
if (remainingWhitespace != null)
{
Accept(remainingWhitespace);
}
var remainingWhitespaceLength = Span.GetContent().Value.Length;
// Consume any other whitespace tokens.
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
var hasTypeError = !At(CSharpSymbolType.Identifier);
if (hasTypeError)
{
Context.OnError(
startLocation,
Resources.FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(InjectKeyword),
InjectKeyword.Length);
}
// Accept 'MyApp.MyService'
NamespaceOrTypeName();
// typeName now contains the token 'MyApp.MyService'
var typeName = Span.GetContent().Value;
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
if (!hasTypeError && (EndOfFile || At(CSharpSymbolType.NewLine)))
{
// Add an error for the property name only if we successfully read the type name
Context.OnError(startLocation,
Resources.FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(InjectKeyword),
keywordwithSingleWhitespaceLength + remainingWhitespaceLength + typeName.Length);
}
// Read until end of line. Span now contains 'MyApp.MyService MyServiceName'.
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);
}
// Parse out 'MyServicePropertyName' from the Span.
var propertyName = Span.GetContent()
.Value
.Substring(typeName.Length);
// ';' is optional
propertyName = RemoveWhitespaceAndTrailingSemicolons(propertyName);
Span.ChunkGenerator = new InjectParameterGenerator(typeName.Trim(), propertyName);
// Output the span and finish the block
CompleteBlock();
Output(SpanKind.Code, AcceptedCharacters.AnyExceptNewline);
}
private SpanChunkGenerator CreateModelChunkGenerator(string model)
{
return new ModelChunkGenerator(_baseType, model);
}
// Internal for unit testing
internal static string RemoveWhitespaceAndTrailingSemicolons(string value)
{
Debug.Assert(value != null);
value = value.TrimStart();
for (var index = value.Length - 1; index >= 0; index--)
{
var currentChar = value[index];
if (!char.IsWhiteSpace(currentChar) && currentChar != ';')
{
return value.Substring(0, index + 1);
}
}
return string.Empty;
}
}
}