// 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; } } }