Restore @model support in RazorHost
This commit is contained in:
parent
4a801b6c7a
commit
5a22d9b52c
|
|
@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.Startu
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.Startup.k10", "src\Microsoft.AspNet.Mvc.Startup\Microsoft.AspNet.Mvc.Startup.k10.csproj", "{43ECCFDF-E646-4766-B339-F5CCD69DD6C3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.Razor.Host.Test.net45", "test\Microsoft.AspNet.Mvc.Razor.Host.Test\Microsoft.AspNet.Mvc.Razor.Host.Test.net45.csproj", "{537CC0EE-4B62-4789-9AE9-94BE28E0D25A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -113,6 +115,10 @@ Global
|
|||
{43ECCFDF-E646-4766-B339-F5CCD69DD6C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{43ECCFDF-E646-4766-B339-F5CCD69DD6C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{43ECCFDF-E646-4766-B339-F5CCD69DD6C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{537CC0EE-4B62-4789-9AE9-94BE28E0D25A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{537CC0EE-4B62-4789-9AE9-94BE28E0D25A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{537CC0EE-4B62-4789-9AE9-94BE28E0D25A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{537CC0EE-4B62-4789-9AE9-94BE28E0D25A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -137,5 +143,6 @@ Global
|
|||
{501817DD-8143-4A50-888D-99896A82CD12} = {222CA408-93EE-473A-9325-D04989EC9FEF}
|
||||
{A7D7CD66-A407-4144-8AB7-07F895F87137} = {CE037E26-9EB5-48E2-B73B-06C6FF6CC9F5}
|
||||
{42195A56-42C0-4CFF-A982-B6E24EFC6356} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{537CC0EE-4B62-4789-9AE9-94BE28E0D25A} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@using MvcSample.Models
|
||||
@model User
|
||||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
ViewBag.Title = "Home Page";
|
||||
|
|
@ -9,11 +10,8 @@
|
|||
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
|
||||
<p><a href="http://asp.net" class="btn btn-primary btn-large">Learn more »</a></p>
|
||||
</div>
|
||||
@{
|
||||
var user = new User { Name = "Test user" };
|
||||
}
|
||||
<div class="row">
|
||||
<h3>Hello @user.Name!</h3>
|
||||
<h3>Hello @Model.Name!</h3>
|
||||
<div class="col-md-4">
|
||||
<h2>Getting started</h2>
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
<div class="container body-content">
|
||||
@RenderBody()
|
||||
<hr />
|
||||
<address>
|
||||
@Model.Address
|
||||
</address>
|
||||
<footer>
|
||||
<p>© @DateTime.Now.Year - My ASP.NET Application</p>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNet.Mvc.Razor.Host;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
|
|
@ -8,13 +9,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
public class MvcRazorCodeParser : CSharpCodeParser
|
||||
{
|
||||
private const string GenericTypeFormat = "{0}<{1}>";
|
||||
private const string ModelKeyword = "model";
|
||||
private const string GenericTypeFormatString = "{0}<{1}>";
|
||||
private readonly string _baseType;
|
||||
private SourceLocation? _endInheritsLocation;
|
||||
private bool _modelStatementFound;
|
||||
|
||||
public MvcRazorCodeParser()
|
||||
public MvcRazorCodeParser(string baseType)
|
||||
{
|
||||
_baseType = baseType;
|
||||
MapDirectives(ModelDirective, ModelKeyword);
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
if (_modelStatementFound && _endInheritsLocation.HasValue)
|
||||
{
|
||||
Context.OnError(_endInheritsLocation.Value, String.Format(CultureInfo.CurrentCulture, "MvcResources.MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword", ModelKeyword));
|
||||
Context.OnError(_endInheritsLocation.Value, Resources.MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(ModelKeyword));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,14 +48,12 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
SourceLocation endModelLocation = CurrentLocation;
|
||||
|
||||
BaseTypeDirective(
|
||||
String.Format(CultureInfo.CurrentCulture,
|
||||
"MvcResources.MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName", ModelKeyword),
|
||||
CreateModelCodeGenerator);
|
||||
BaseTypeDirective(Resources.MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName(ModelKeyword),
|
||||
CreateModelCodeGenerator);
|
||||
|
||||
if (_modelStatementFound)
|
||||
{
|
||||
Context.OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture, "MvcResources.MvcRazorCodeParser_OnlyOneModelStatementIsAllowed", ModelKeyword));
|
||||
Context.OnError(endModelLocation, Resources.MvcRazorCodeParser_OnlyOneModelStatementIsAllowed(ModelKeyword));
|
||||
}
|
||||
|
||||
_modelStatementFound = true;
|
||||
|
|
@ -62,7 +63,12 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
private SpanCodeGenerator CreateModelCodeGenerator(string model)
|
||||
{
|
||||
return new SetModelTypeCodeGenerator(model, GenericTypeFormatString);
|
||||
// In the event we have an empty model, the name we generate does not matter since it's a parser error.
|
||||
// We'll use the non-generic version of the base type.
|
||||
string baseType = String.IsNullOrEmpty(model) ?
|
||||
_baseType :
|
||||
String.Format(CultureInfo.InvariantCulture, GenericTypeFormat, _baseType, model);
|
||||
return new SetBaseTypeCodeGenerator(baseType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.IO;
|
|||
using System.Text;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
|
|
@ -17,16 +18,20 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
"Microsoft.AspNet.Mvc.Razor"
|
||||
};
|
||||
|
||||
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
|
||||
// This field holds the type name without the generic decoration (MyBaseType)
|
||||
private readonly string _baseType;
|
||||
|
||||
public MvcRazorHost(Type baseType)
|
||||
: this(baseType.FullName)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public MvcRazorHost(string baseType)
|
||||
: base(new CSharpRazorCodeLanguage())
|
||||
{
|
||||
DefaultBaseClass = baseType;
|
||||
_baseType = baseType;
|
||||
DefaultBaseClass = baseType + "<dynamic>";
|
||||
GeneratedClassContext = new GeneratedClassContext(
|
||||
executeMethodName: "Execute",
|
||||
writeMethodName: "Write",
|
||||
|
|
@ -61,6 +66,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
|
||||
{
|
||||
return new MvcRazorCodeParser(_baseType);
|
||||
}
|
||||
|
||||
private static string GenerateNamespace(string rootRelativePath)
|
||||
{
|
||||
var namespaceBuilder = new StringBuilder(rootRelativePath.Length);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
// <auto-generated />
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Host
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.Mvc.Razor.Host.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
|
||||
/// </summary>
|
||||
internal static string MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' keyword must be followed by a type name on the same line.
|
||||
/// </summary>
|
||||
internal static string MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only one '{0}' statement is allowed in a file.
|
||||
/// </summary>
|
||||
internal static string MvcRazorCodeParser_OnlyOneModelStatementIsAllowed(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name)
|
||||
{
|
||||
string value = _resourceManager.GetString(name);
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword" xml:space="preserve">
|
||||
<value>The 'inherits' keyword is not allowed when a '{0}' keyword is used.</value>
|
||||
</data>
|
||||
<data name="MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName" xml:space="preserve">
|
||||
<value>The '{0}' keyword must be followed by a type name on the same line.</value>
|
||||
</data>
|
||||
<data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">
|
||||
<value>Only one '{0}' statement is allowed in a file.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
internal class SetModelTypeCodeGenerator : SetBaseTypeCodeGenerator
|
||||
{
|
||||
private string _genericTypeFormat;
|
||||
|
||||
public SetModelTypeCodeGenerator(string modelType, string genericTypeFormat)
|
||||
: base(modelType)
|
||||
{
|
||||
_genericTypeFormat = genericTypeFormat;
|
||||
}
|
||||
|
||||
protected override string ResolveType(CodeGeneratorContext context, string baseType)
|
||||
{
|
||||
return String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_genericTypeFormat,
|
||||
context.Host.DefaultBaseClass,
|
||||
baseType);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Model:" + BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Startup
|
|||
|
||||
AddInstance<IControllerDescriptorProvider>(provider);
|
||||
AddInstance<IFileSystem>(new PhysicalFileSystem(appRoot));
|
||||
AddInstance<IMvcRazorHost>(new MvcRazorHost("Microsoft.AspNet.Mvc.Razor.RazorView<dynamic>"));
|
||||
AddInstance<IMvcRazorHost>(new MvcRazorHost(typeof(RazorView).FullName));
|
||||
|
||||
#if NET45
|
||||
Add<ICompilationService, CscBasedCompilationService>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,292 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Host.Test
|
||||
{
|
||||
public class MvcRazorCodeParserTest
|
||||
{
|
||||
private const string DefaultBaseType = "Microsoft.AspNet.ViewPage";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_AddsModelKeyword()
|
||||
{
|
||||
var parser = new TestMvcCSharpRazorCodeParser();
|
||||
|
||||
Assert.True(parser.HasDirective("model"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesSingleInstance()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = "@model Foo";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" Foo")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<Foo>"))
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesNullableTypes()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = "@model Foo?\r\nBar";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo?\r\n")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<Foo?>")),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupCodeGenerator())
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesArrays()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = "@model Foo[[]][]\r\nBar";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo[[]][]\r\n")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<Foo[[]][]>")),
|
||||
factory.Markup("Bar")
|
||||
.With(new MarkupCodeGenerator())
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_HandlesVSTemplateSyntax()
|
||||
{
|
||||
// Arrange + Act
|
||||
var document = "@model $rootnamespace$.MyModel";
|
||||
var spans = ParseDocument(document);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("$rootnamespace$.MyModel")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<$rootnamespace$.MyModel>"))
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnMissingModelType()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document = "@model ";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code(" ")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType)),
|
||||
};
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("The 'model' keyword must be followed by a type name on the same line.", new SourceLocation(9, 0, 9), 1)
|
||||
};
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnMultipleModelStatements()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document =
|
||||
"@model Foo" + Environment.NewLine
|
||||
+ "@model Bar";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo\r\n")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<Foo>")),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<Bar>"))
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("Only one 'model' statement is allowed in a file.", new SourceLocation(18, 1, 6), 1)
|
||||
};
|
||||
expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnModelFollowedByInherits()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document =
|
||||
"@model Foo" + Environment.NewLine
|
||||
+ "@inherits Bar";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo\r\n")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<Foo>")),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inherits ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar")
|
||||
.As(new SetBaseTypeCodeGenerator("Bar"))
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("The 'inherits' keyword is not allowed when a 'model' keyword is used.", new SourceLocation(21, 1, 9), 1)
|
||||
};
|
||||
expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseModelKeyword_ErrorOnInheritsFollowedByModel()
|
||||
{
|
||||
// Arrange + Act
|
||||
List<RazorError> errors = new List<RazorError>();
|
||||
var document =
|
||||
"@inherits Bar" + Environment.NewLine
|
||||
+ "@model Foo";
|
||||
var spans = ParseDocument(document, errors);
|
||||
|
||||
// Assert
|
||||
var factory = SpanFactory.CreateCsHtml();
|
||||
var expectedSpans = new Span[]
|
||||
{
|
||||
factory.EmptyHtml(),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("inherits ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Bar" + Environment.NewLine)
|
||||
.As(new SetBaseTypeCodeGenerator("Bar")),
|
||||
factory.CodeTransition(SyntaxConstants.TransitionString)
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.MetaCode("model ")
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory.Code("Foo")
|
||||
.As(new SetBaseTypeCodeGenerator(DefaultBaseType + "<Foo>"))
|
||||
};
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError("The 'inherits' keyword is not allowed when a 'model' keyword is used.", new SourceLocation(9, 0, 9), 1)
|
||||
};
|
||||
expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
|
||||
Assert.Equal(expectedSpans, spans.ToArray());
|
||||
Assert.Equal(expectedErrors, errors.ToArray());
|
||||
}
|
||||
|
||||
private static List<Span> ParseDocument(string documentContents, IList<RazorError> errors = null)
|
||||
{
|
||||
errors = errors ?? new List<RazorError>();
|
||||
var markupParser = new HtmlMarkupParser();
|
||||
var codeParser = new TestMvcCSharpRazorCodeParser();
|
||||
var context = new ParserContext(new SeekableTextReader(documentContents), codeParser, markupParser, markupParser);
|
||||
codeParser.Context = context;
|
||||
markupParser.Context = context;
|
||||
markupParser.ParseDocument();
|
||||
|
||||
ParserResults results = context.CompleteParse();
|
||||
foreach (RazorError error in results.ParserErrors)
|
||||
{
|
||||
errors.Add(error);
|
||||
}
|
||||
return results.Document.Flatten().ToList();
|
||||
}
|
||||
|
||||
private sealed class TestMvcCSharpRazorCodeParser : MvcRazorCodeParser
|
||||
{
|
||||
public TestMvcCSharpRazorCodeParser(string baseType = DefaultBaseType)
|
||||
: base(baseType)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool HasDirective(string directive)
|
||||
{
|
||||
Action handler;
|
||||
return TryGetDirectiveHandler(directive, out handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Editor;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
using Microsoft.AspNet.Razor.Tokenizer;
|
||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Host.Test
|
||||
{
|
||||
public static class SpanFactoryExtensions
|
||||
{
|
||||
public static SpanConstructor EmptyHtml(this SpanFactory self)
|
||||
{
|
||||
return self.Span(SpanKind.Markup, new HtmlSymbol(self.LocationTracker.CurrentLocation, String.Empty, HtmlSymbolType.Unknown))
|
||||
.With(new MarkupCodeGenerator());
|
||||
}
|
||||
|
||||
public static UnclassifiedCodeSpanConstructor Code(this SpanFactory self, string content)
|
||||
{
|
||||
return new UnclassifiedCodeSpanConstructor(
|
||||
self.Span(SpanKind.Code, content, markup: false));
|
||||
}
|
||||
|
||||
public static SpanConstructor CodeTransition(this SpanFactory self, string content)
|
||||
{
|
||||
return self.Span(SpanKind.Transition, content, markup: false).Accepts(AcceptedCharacters.None);
|
||||
}
|
||||
|
||||
public static SpanConstructor MetaCode(this SpanFactory self, string content)
|
||||
{
|
||||
return self.Span(SpanKind.MetaCode, content, markup: false);
|
||||
}
|
||||
public static SpanConstructor Markup(this SpanFactory self, string content)
|
||||
{
|
||||
return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
|
||||
}
|
||||
}
|
||||
|
||||
public class SpanFactory
|
||||
{
|
||||
public Func<ITextDocument, ITokenizer> MarkupTokenizerFactory { get; set; }
|
||||
public Func<ITextDocument, ITokenizer> CodeTokenizerFactory { get; set; }
|
||||
public SourceLocationTracker LocationTracker { get; private set; }
|
||||
|
||||
public static SpanFactory CreateCsHtml()
|
||||
{
|
||||
return new SpanFactory()
|
||||
{
|
||||
MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
|
||||
CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
|
||||
};
|
||||
}
|
||||
|
||||
public SpanFactory()
|
||||
{
|
||||
LocationTracker = new SourceLocationTracker();
|
||||
}
|
||||
|
||||
|
||||
public SpanConstructor Span(SpanKind kind, string content, bool markup)
|
||||
{
|
||||
return new SpanConstructor(kind, Tokenize(new[] { content }, markup));
|
||||
}
|
||||
|
||||
public SpanConstructor Span(SpanKind kind, params ISymbol[] symbols)
|
||||
{
|
||||
return new SpanConstructor(kind, symbols);
|
||||
}
|
||||
|
||||
private IEnumerable<ISymbol> Tokenize(IEnumerable<string> contentFragments, bool markup)
|
||||
{
|
||||
return contentFragments.SelectMany(fragment => Tokenize(fragment, markup));
|
||||
}
|
||||
|
||||
private IEnumerable<ISymbol> Tokenize(string content, bool markup)
|
||||
{
|
||||
ITokenizer tok = MakeTokenizer(markup, new SeekableTextReader(content));
|
||||
ISymbol sym;
|
||||
ISymbol last = null;
|
||||
while ((sym = tok.NextSymbol()) != null)
|
||||
{
|
||||
OffsetStart(sym, LocationTracker.CurrentLocation);
|
||||
last = sym;
|
||||
yield return sym;
|
||||
}
|
||||
LocationTracker.UpdateLocation(content);
|
||||
}
|
||||
|
||||
private ITokenizer MakeTokenizer(bool markup, SeekableTextReader seekableTextReader)
|
||||
{
|
||||
if (markup)
|
||||
{
|
||||
return MarkupTokenizerFactory(seekableTextReader);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CodeTokenizerFactory(seekableTextReader);
|
||||
}
|
||||
}
|
||||
|
||||
private void OffsetStart(ISymbol sym, SourceLocation sourceLocation)
|
||||
{
|
||||
sym.OffsetStart(sourceLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpanConstructorExtensions
|
||||
{
|
||||
public static SpanConstructor Accepts(this SpanConstructor self, AcceptedCharacters accepted)
|
||||
{
|
||||
return self.With(eh => eh.AcceptedCharacters = accepted);
|
||||
}
|
||||
}
|
||||
|
||||
public class UnclassifiedCodeSpanConstructor
|
||||
{
|
||||
SpanConstructor _self;
|
||||
|
||||
public UnclassifiedCodeSpanConstructor(SpanConstructor self)
|
||||
{
|
||||
_self = self;
|
||||
}
|
||||
|
||||
public SpanConstructor As(ISpanCodeGenerator codeGenerator)
|
||||
{
|
||||
return _self.With(codeGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
public class SpanConstructor
|
||||
{
|
||||
public SpanBuilder Builder { get; private set; }
|
||||
|
||||
internal static IEnumerable<ISymbol> TestTokenizer(string str)
|
||||
{
|
||||
yield return new RawTextSymbol(SourceLocation.Zero, str);
|
||||
}
|
||||
|
||||
public SpanConstructor(SpanKind kind, IEnumerable<ISymbol> symbols)
|
||||
{
|
||||
Builder = new SpanBuilder();
|
||||
Builder.Kind = kind;
|
||||
Builder.EditHandler = SpanEditHandler.CreateDefault(TestTokenizer);
|
||||
foreach (ISymbol sym in symbols)
|
||||
{
|
||||
Builder.Accept(sym);
|
||||
}
|
||||
}
|
||||
|
||||
private Span Build()
|
||||
{
|
||||
return Builder.Build();
|
||||
}
|
||||
|
||||
public SpanConstructor With(ISpanCodeGenerator generator)
|
||||
{
|
||||
Builder.CodeGenerator = generator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpanConstructor With(SpanEditHandler handler)
|
||||
{
|
||||
Builder.EditHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpanConstructor With(Action<SpanEditHandler> handlerConfigurer)
|
||||
{
|
||||
handlerConfigurer(Builder.EditHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static implicit operator Span(SpanConstructor self)
|
||||
{
|
||||
return self.Build();
|
||||
}
|
||||
}
|
||||
|
||||
internal class RawTextSymbol : ISymbol
|
||||
{
|
||||
public SourceLocation Start { get; private set; }
|
||||
public string Content { get; private set; }
|
||||
|
||||
public RawTextSymbol(SourceLocation start, string content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException("content");
|
||||
}
|
||||
|
||||
Start = start;
|
||||
Content = content;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
RawTextSymbol other = obj as RawTextSymbol;
|
||||
return Equals(Start, other.Start) && Equals(Content, other.Content);
|
||||
}
|
||||
|
||||
internal bool EquivalentTo(ISymbol sym)
|
||||
{
|
||||
return Equals(Start, sym.Start) && Equals(Content, sym.Content);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Start.GetHashCode();
|
||||
}
|
||||
|
||||
public void OffsetStart(SourceLocation documentStart)
|
||||
{
|
||||
Start = documentStart + Start;
|
||||
}
|
||||
|
||||
public void ChangeStart(SourceLocation newStart)
|
||||
{
|
||||
Start = newStart;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "{0} RAW - [{1}]", Start, Content);
|
||||
}
|
||||
|
||||
internal void CalculateStart(Span prev)
|
||||
{
|
||||
if (prev == null)
|
||||
{
|
||||
Start = SourceLocation.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
Start = new SourceLocationTracker(prev.Start).UpdateLocation(prev.Content).CurrentLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version" : "0.1-alpha-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Razor" : "0.1-alpha-*",
|
||||
"Microsoft.AspNet.Mvc.Razor.Host" : "",
|
||||
"Moq": "4.0.10827",
|
||||
"Xunit": "1.9.1",
|
||||
"Xunit.extensions": "1.9.1"
|
||||
},
|
||||
"configurations": {
|
||||
"net45": { }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue