diff --git a/.gitignore b/.gitignore index 17bbb1698a..db5635f437 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ project.lock.json .build/ .vs/ launchSettings.json -global.json \ No newline at end of file +global.json diff --git a/Razor.sln b/Razor.sln index 136a526e59..3c4d124d68 100644 --- a/Razor.sln +++ b/Razor.sln @@ -43,6 +43,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPageGenerator.Test", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.LanguageServices.Razor.Test", "test\Microsoft.VisualStudio.LanguageServices.Razor.Test\Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj", "{37E61BDB-658E-4F44-A499-D64CC6D35485}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.Extensions", "src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj", "{995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Test", "test\Microsoft.AspNetCore.Mvc.Razor.Extensions.Test\Microsoft.AspNetCore.Mvc.Razor.Extensions.Test.csproj", "{7CFD5646-A757-4498-9E01-9C8528ED60AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -161,6 +165,22 @@ Global {37E61BDB-658E-4F44-A499-D64CC6D35485}.Release|Any CPU.Build.0 = Release|Any CPU {37E61BDB-658E-4F44-A499-D64CC6D35485}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU {37E61BDB-658E-4F44-A499-D64CC6D35485}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.Release|Any CPU.Build.0 = Release|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.Release|Any CPU.Build.0 = Release|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU + {7CFD5646-A757-4498-9E01-9C8528ED60AE}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -180,5 +200,7 @@ Global {E5D92DB7-5CBF-410A-9685-FF76F71EC96F} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED} {96EB1BD4-B8E0-4F52-A068-BBCACA7E3F63} = {92463391-81BE-462B-AC3C-78C6C760741F} {37E61BDB-658E-4F44-A499-D64CC6D35485} = {92463391-81BE-462B-AC3C-78C6C760741F} + {995F2FEB-65FA-4399-B1C0-16E0B3FBDB15} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED} + {7CFD5646-A757-4498-9E01-9C8528ED60AE} = {92463391-81BE-462B-AC3C-78C6C760741F} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/IInjectDirectiveTargetExtension.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/IInjectDirectiveTargetExtension.cs new file mode 100644 index 0000000000..3b9a51d6b8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/IInjectDirectiveTargetExtension.cs @@ -0,0 +1,12 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public interface IInjectDirectiveTargetExtension : IRuntimeTargetExtension + { + void WriteInjectProperty(CSharpRenderingContext context, InjectDirectiveIRNode node); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs new file mode 100644 index 0000000000..fa4a44851e --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs @@ -0,0 +1,90 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public static class InjectDirective + { + public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("inject").AddType().AddMember().Build(); + + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) + { + builder.AddDirective(Directive); + builder.Features.Add(new Pass()); + return builder; + } + + internal class Pass : RazorIRPassBase, IRazorDirectiveClassifierPass + { + public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + { + var visitor = new Visitor(); + visitor.Visit(irDocument); + var modelType = ModelDirective.GetModelType(irDocument); + + var properties = new HashSet(StringComparer.Ordinal); + + for (var i = visitor.Directives.Count - 1; i >= 0; i--) + { + var directive = visitor.Directives[i]; + var tokens = directive.Tokens.ToArray(); + if (tokens.Length < 2) + { + continue; + } + + var typeName = tokens[0].Content; + var memberName = tokens[1].Content; + + if (!properties.Add(memberName)) + { + continue; + } + + typeName = typeName.Replace("", "<" + modelType + ">"); + + var injectNode = new InjectDirectiveIRNode() + { + TypeName = typeName, + MemberName = memberName, + Source = directive.Source, + Parent = visitor.Class, + }; + + visitor.Class.Children.Add(injectNode); + } + } + } + + private class Visitor : RazorIRNodeWalker + { + public ClassDeclarationIRNode Class { get; private set; } + + public IList Directives { get; } = new List(); + + public override void VisitClass(ClassDeclarationIRNode node) + { + if (Class == null) + { + Class = node; + } + + base.VisitClass(node); + } + + public override void VisitDirective(DirectiveIRNode node) + { + if (node.Descriptor == Directive) + { + Directives.Add(node); + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirectiveIRNode.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirectiveIRNode.cs new file mode 100644 index 0000000000..975dc17ac1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirectiveIRNode.cs @@ -0,0 +1,45 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class InjectDirectiveIRNode : ExtensionIRNode + { + public string TypeName { get; set; } + + public string MemberName { get; set; } + + public override IList Children { get; } = new RazorIRNode[0]; + + public override RazorIRNode Parent { get; set; } + + public override SourceSpan? Source { get; set; } + + public override void Accept(RazorIRNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(this, visitor); + } + + public override void WriteNode(RuntimeTarget target, CSharpRenderingContext context) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + var extension = target.GetExtension(); + extension.WriteInjectProperty(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirectiveTargetExtension.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirectiveTargetExtension.cs new file mode 100644 index 0000000000..b3b619205c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirectiveTargetExtension.cs @@ -0,0 +1,44 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class InjectDirectiveTargetExtension : IInjectDirectiveTargetExtension + { + private const string RazorInjectAttribute = "[global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute]"; + + public void WriteInjectProperty(CSharpRenderingContext context, InjectDirectiveIRNode node) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + var property = $"public {node.TypeName} {node.MemberName} {{ get; private set; }}"; + + if (node.Source.HasValue) + { + using (context.Writer.BuildLinePragma(node.Source.Value)) + { + context.Writer + .WriteLine(RazorInjectAttribute) + .WriteLine(property); + } + } + else + { + context.Writer + .WriteLine(RazorInjectAttribute) + .WriteLine(property); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/ClassName.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/ClassName.cs new file mode 100644 index 0000000000..2dbbd06105 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/ClassName.cs @@ -0,0 +1,63 @@ +// 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.Globalization; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal +{ + public static class ClassName + { + public static string GetClassNameFromPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + return path; + } + + return SanitizeClassName(path); + } + + // CSharp Spec §2.4.2 + private static bool IsIdentifierStart(char character) + { + return char.IsLetter(character) || + character == '_' || + CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.LetterNumber; + } + + public static bool IsIdentifierPart(char character) + { + return char.IsDigit(character) || + IsIdentifierStart(character) || + IsIdentifierPartByUnicodeCategory(character); + } + + private static bool IsIdentifierPartByUnicodeCategory(char character) + { + var category = CharUnicodeInfo.GetUnicodeCategory(character); + + return category == UnicodeCategory.NonSpacingMark || // Mn + category == UnicodeCategory.SpacingCombiningMark || // Mc + category == UnicodeCategory.ConnectorPunctuation || // Pc + category == UnicodeCategory.Format; // Cf + } + + private static string SanitizeClassName(string inputName) + { + if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0])) + { + inputName = "_" + inputName; + } + + var builder = new InplaceStringBuilder(inputName.Length); + for (var i = 0; i < inputName.Length; i++) + { + var ch = inputName[i]; + builder.Append(IsIdentifierPart(ch) ? ch : '_'); + } + + return builder.ToString(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/RazorCodeDocumentExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/RazorCodeDocumentExtensions.cs new file mode 100644 index 0000000000..a1a90f2d1a --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/RazorCodeDocumentExtensions.cs @@ -0,0 +1,34 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal +{ + public static class RazorCodeDocumentExtensions + { + private const string RelativePathKey = "relative-path"; + + public static string GetRelativePath(this RazorCodeDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return document.Items[RelativePathKey] as string; + } + + + public static void SetRelativePath(this RazorCodeDocument document, string relativePath) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + document.Items[RelativePathKey] = relativePath; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj new file mode 100644 index 0000000000..6197aaced9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj @@ -0,0 +1,20 @@ + + + + + + ASP.NET Core design time hosting infrastructure for the Razor view engine. + net451;netstandard1.6 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc;cshtml;razor + + + + + + + + + + diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs new file mode 100644 index 0000000000..9903eb40bd --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs @@ -0,0 +1,117 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public static class ModelDirective + { + public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("model").AddType().Build(); + + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) + { + builder.AddDirective(Directive); + builder.Features.Add(new Pass()); + return builder; + } + + public static string GetModelType(DocumentIRNode document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + var visitor = new Visitor(); + return GetModelType(document, visitor); + } + + private static string GetModelType(DocumentIRNode document, Visitor visitor) + { + visitor.Visit(document); + + for (var i = visitor.ModelDirectives.Count - 1; i >= 0; i--) + { + var directive = visitor.ModelDirectives[i]; + + var tokens = directive.Tokens.ToArray(); + if (tokens.Length >= 1) + { + return tokens[0].Content; + } + } + + if (document.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind) + { + return visitor.Class.Name; + } + else + { + return "dynamic"; + } + } + + internal class Pass : RazorIRPassBase, IRazorDirectiveClassifierPass + { + // Runs after the @inherits directive + public override int Order => 5; + + public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + { + var visitor = new Visitor(); + var modelType = GetModelType(irDocument, visitor); + + var baseType = visitor.Class?.BaseType?.Replace("", "<" + modelType + ">"); + for (var i = visitor.InheritsDirectives.Count - 1; i >= 0; i--) + { + var directive = visitor.InheritsDirectives[i]; + var tokens = directive.Tokens.ToArray(); + if (tokens.Length >= 1) + { + baseType = tokens[0].Content.Replace("", "<" + modelType + ">"); + tokens[0].Content = baseType; + break; + } + } + + visitor.Class.BaseType = baseType; + } + } + + private class Visitor : RazorIRNodeWalker + { + public ClassDeclarationIRNode Class { get; private set; } + + public IList InheritsDirectives { get; } = new List(); + + public IList ModelDirectives { get; } = new List(); + + public override void VisitClass(ClassDeclarationIRNode node) + { + if (Class == null) + { + Class = node; + } + + base.VisitClass(node); + } + + public override void VisitDirective(DirectiveIRNode node) + { + if (node.Descriptor == Directive) + { + ModelDirectives.Add(node); + } + else if (node.Descriptor.Name == "inherits") + { + InheritsDirectives.Add(node); + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelExpressionPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelExpressionPass.cs new file mode 100644 index 0000000000..090534e792 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelExpressionPass.cs @@ -0,0 +1,109 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class ModelExpressionPass : RazorIRPassBase, IRazorIROptimizationPass + { + private const string ModelExpressionTypeName = "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression"; + + public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + { + var visitor = new Visitor(); + visitor.Visit(irDocument); + } + + private class Visitor : RazorIRNodeWalker + { + public List TagHelpers { get; } = new List(); + + public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node) + { + if (string.Equals(node.Descriptor.TypeName, ModelExpressionTypeName, StringComparison.Ordinal) || + (node.IsIndexerNameMatch && + string.Equals(node.Descriptor.IndexerTypeName, ModelExpressionTypeName, StringComparison.Ordinal))) + { + var expression = new CSharpExpressionIRNode(); + var builder = RazorIRBuilder.Create(expression); + + builder.Add(new RazorIRToken() + { + Kind = RazorIRToken.TokenKind.CSharp, + Content = "ModelExpressionProvider.CreateModelExpression(ViewData, __model => ", + }); + + if (node.Children.Count == 1 && node.Children[0] is HtmlContentIRNode) + { + // A 'simple' expression will look like __model => __model.Foo + // + // Note that the fact we're looking for HTML here is based on a bug. + // https://github.com/aspnet/Razor/issues/963 + var original = ((HtmlContentIRNode)node.Children[0]); + + builder.Add(new RazorIRToken() + { + Kind = RazorIRToken.TokenKind.CSharp, + Content = "__model." + }); + + builder.Add(new RazorIRToken() + { + Kind = RazorIRToken.TokenKind.CSharp, + Content = original.Content, + Source = original.Source, + }); + } + else + { + for (var i = 0; i < node.Children.Count; i++) + { + var nestedExpression = node.Children[i] as CSharpExpressionIRNode; + if (nestedExpression != null) + { + for (var j = 0; j < nestedExpression.Children.Count; j++) + { + var cSharpToken = nestedExpression.Children[j] as RazorIRToken; + if (cSharpToken != null && cSharpToken.Kind == RazorIRToken.TokenKind.CSharp) + { + builder.Add(cSharpToken); + } + } + + continue; + } + + // Note that the fact we're looking for HTML here is based on a bug. + // https://github.com/aspnet/Razor/issues/963 + var html = node.Children[i] as HtmlContentIRNode; + if (html != null) + { + builder.Add(new RazorIRToken() + { + Kind = RazorIRToken.TokenKind.CSharp, + Content = html.Content, + Source = html.Source, + }); + } + } + } + + builder.Add(new RazorIRToken() + { + Kind = RazorIRToken.TokenKind.CSharp, + Content = ")", + }); + + node.Children.Clear(); + + node.Children.Add(expression); + expression.Parent = node; + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcRazorTemplateEngine.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcRazorTemplateEngine.cs new file mode 100644 index 0000000000..0375af3552 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcRazorTemplateEngine.cs @@ -0,0 +1,62 @@ +// 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.IO; +using System.Text; +using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; +using Microsoft.AspNetCore.Razor.Evolution; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + /// + /// A for Mvc Razor views. + /// + public class MvcRazorTemplateEngine : RazorTemplateEngine + { + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + public MvcRazorTemplateEngine( + RazorEngine engine, + RazorProject project) + : base(engine, project) + { + Options.DefaultImports = GetDefaultImports(); + } + + /// + public override RazorCodeDocument CreateCodeDocument(RazorProjectItem projectItem) + { + var codeDocument = base.CreateCodeDocument(projectItem); + codeDocument.SetRelativePath(projectItem.Path); + + return codeDocument; + } + + private static RazorSourceDocument GetDefaultImports() + { + using (var stream = new MemoryStream()) + using (var writer = new StreamWriter(stream, Encoding.UTF8)) + { + writer.WriteLine("@using System"); + writer.WriteLine("@using System.Linq"); + writer.WriteLine("@using System.Collections.Generic"); + writer.WriteLine("@using Microsoft.AspNetCore.Mvc"); + writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering"); + writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures"); + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html"); + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json"); + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component"); + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url"); + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider"); + writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"); + writer.Flush(); + + stream.Position = 0; + return RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs new file mode 100644 index 0000000000..9fb4c91502 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs @@ -0,0 +1,37 @@ +// 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 Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class MvcViewDocumentClassifierPass : DocumentClassifierPassBase + { + public readonly string MvcViewDocumentKind = "mvc.1.0.view"; + + protected override string DocumentKind => MvcViewDocumentKind; + + protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument) => true; + + protected override void OnDocumentStructureCreated( + RazorCodeDocument codeDocument, + NamespaceDeclarationIRNode @namespace, + ClassDeclarationIRNode @class, + RazorMethodDeclarationIRNode method) + { + var filePath = codeDocument.GetRelativePath() ?? codeDocument.Source.FileName; + + base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); + @class.Name = ClassName.GetClassNameFromPath(filePath); + @class.BaseType = "global::Microsoft.AspNetCore.Razor.RazorPage"; + @class.AccessModifier = "public"; + @namespace.Content = "AspNetCore"; + method.Name = "ExecuteAsync"; + method.Modifiers = new[] { "async", "override" }; + method.AccessModifier = "public"; + method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}"; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs new file mode 100644 index 0000000000..4cb8cc4664 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs @@ -0,0 +1,52 @@ +// 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.Linq; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public static class PageDirective + { + public static readonly DirectiveDescriptor DirectiveDescriptor = DirectiveDescriptorBuilder + .Create("page") + .BeginOptionals() + .AddString() + .Build(); + + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) + { + builder.AddDirective(DirectiveDescriptor); + return builder; + } + + public static bool TryGetRouteTemplate(DocumentIRNode irDocument, out string routeTemplate) + { + var visitor = new Visitor(); + for (var i = 0; i < irDocument.Children.Count; i++) + { + visitor.Visit(irDocument.Children[i]); + } + + routeTemplate = visitor.RouteTemplate; + return visitor.DirectiveNode != null; + } + + private class Visitor : RazorIRNodeWalker + { + public DirectiveIRNode DirectiveNode { get; private set; } + + public string RouteTemplate { get; private set; } + + public override void VisitDirective(DirectiveIRNode node) + { + if (node.Descriptor == DirectiveDescriptor) + { + DirectiveNode = node; + RouteTemplate = node.Tokens.FirstOrDefault()?.Content; + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PagesPropertyInjectionPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PagesPropertyInjectionPass.cs new file mode 100644 index 0000000000..f03a1aa678 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PagesPropertyInjectionPass.cs @@ -0,0 +1,55 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class PagesPropertyInjectionPass : RazorIRPassBase, IRazorIROptimizationPass + { + public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + { + if (irDocument.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind) + { + return; + } + + var modelType = ModelDirective.GetModelType(irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + var @class = visitor.Class; + + var viewDataType = $"global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<{modelType}>"; + var vddProperty = new CSharpStatementIRNode + { + Content = $"public {viewDataType} ViewData => ({viewDataType})PageContext?.ViewData;", + Parent = @class, + }; + var modelProperty = new CSharpStatementIRNode + { + Content = $"public {modelType} Model => ViewData.Model;", + Parent = @class, + }; + + @class.Children.Add(vddProperty); + @class.Children.Add(modelProperty); + } + + private class Visitor : RazorIRNodeWalker + { + public ClassDeclarationIRNode Class { get; private set; } + + public override void VisitClass(ClassDeclarationIRNode node) + { + if (Class == null) + { + Class = node; + } + + base.VisitClass(node); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..6a4431ff38 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..24477b448a --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/Resources.Designer.cs @@ -0,0 +1,126 @@ +// +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Razor.Extensions.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Value cannot be null or empty. + /// + internal static string ArgumentCannotBeNullOrEmpy + { + get { return GetString("ArgumentCannotBeNullOrEmpy"); } + } + + /// + /// Value cannot be null or empty. + /// + internal static string FormatArgumentCannotBeNullOrEmpy() + { + return GetString("ArgumentCannotBeNullOrEmpy"); + } + + /// + /// The 'inherits' keyword is not allowed when a '{0}' keyword is used. + /// + internal static string MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword + { + get { return GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"); } + } + + /// + /// The 'inherits' keyword is not allowed when a '{0}' keyword is used. + /// + internal static string FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"), p0); + } + + /// + /// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'. + /// + internal static string MvcRazorCodeParser_InjectDirectivePropertyNameRequired + { + get { return GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"); } + } + + /// + /// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'. + /// + internal static string FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"), p0); + } + + /// + /// The '{0}' keyword must be followed by a type name on the same line. + /// + internal static string MvcRazorCodeParser_KeywordMustBeFollowedByTypeName + { + get { return GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"); } + } + + /// + /// The '{0}' keyword must be followed by a type name on the same line. + /// + internal static string FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"), p0); + } + + /// + /// Only one '{0}' statement is allowed in a file. + /// + internal static string MvcRazorCodeParser_OnlyOneModelStatementIsAllowed + { + get { return GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"); } + } + + /// + /// Only one '{0}' statement is allowed in a file. + /// + internal static string FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0); + } + + /// + /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'. + /// + internal static string MvcRazorParser_InvalidPropertyType + { + get { return GetString("MvcRazorParser_InvalidPropertyType"); } + } + + /// + /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'. + /// + internal static string FormatMvcRazorParser_InvalidPropertyType(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorParser_InvalidPropertyType"), p0, p1, p2); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs new file mode 100644 index 0000000000..003cb27b0b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs @@ -0,0 +1,41 @@ +// 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 Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class RazorPageDocumentClassifierPass : DocumentClassifierPassBase + { + public static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page"; + + protected override string DocumentKind => RazorPageDocumentKind; + + protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + { + string routePrefix; + return PageDirective.TryGetRouteTemplate(irDocument, out routePrefix); + } + + protected override void OnDocumentStructureCreated( + RazorCodeDocument codeDocument, + NamespaceDeclarationIRNode @namespace, + ClassDeclarationIRNode @class, + RazorMethodDeclarationIRNode method) + { + var filePath = codeDocument.GetRelativePath() ?? codeDocument.Source.FileName; + + base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); + @class.BaseType = "global::Microsoft.AspNetCore.RazorPages.Page"; + @class.Name = ClassName.GetClassNameFromPath(filePath); + @class.AccessModifier = "public"; + @namespace.Content = "AspNetCore"; + method.Name = "ExecuteAsync"; + method.Modifiers = new[] { "async", "override" }; + method.AccessModifier = "public"; + method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}"; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx new file mode 100644 index 0000000000..a1c3afc652 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Resources.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Value cannot be null or empty. + + + The 'inherits' keyword is not allowed when a '{0}' keyword is used. + + + A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'. + + + The '{0}' keyword must be followed by a type name on the same line. + + + Only one '{0}' statement is allowed in a file. + + + Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'. + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperDescriptorConventions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperDescriptorConventions.cs new file mode 100644 index 0000000000..0771c6a569 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperDescriptorConventions.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + /// + /// A library of methods used to generate s for view components. + /// + public static class ViewComponentTagHelperDescriptorConventions + { + /// + /// The key in a containing + /// the short name of a view component. + /// + public static readonly string ViewComponentNameKey = "ViewComponentName"; + + /// + /// Indicates whether a represents a view component. + /// + /// The to check. + /// Whether a represents a view component. + public static bool IsViewComponentDescriptor(TagHelperDescriptor descriptor) => + descriptor != null && descriptor.Metadata.ContainsKey(ViewComponentNameKey); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs new file mode 100644 index 0000000000..3646280938 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs @@ -0,0 +1,276 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class ViewComponentTagHelperPass : RazorIRPassBase, IRazorIROptimizationPass + { + public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + { + var visitor = new Visitor(); + visitor.Visit(irDocument); + + if (visitor.Class == null || visitor.TagHelpers.Count == 0) + { + // Nothing to do, bail. + return; + } + + foreach (var tagHelper in visitor.TagHelpers) + { + GenerateVCTHClass(visitor.Class, tagHelper.Value); + + var tagHelperTypeName = tagHelper.Value.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + if (visitor.Fields.UsedTagHelperTypeNames.Remove(tagHelperTypeName)) + { + visitor.Fields.UsedTagHelperTypeNames.Add(GetVCTHFullName(visitor.Namespace, visitor.Class, tagHelper.Value)); + } + } + + foreach (var createNode in visitor.CreateTagHelpers) + { + RewriteCreateNode(visitor.Namespace, visitor.Class, createNode); + } + } + + private void GenerateVCTHClass(ClassDeclarationIRNode @class, TagHelperDescriptor tagHelper) + { + var writer = new CSharpCodeWriter(); + WriteClass(writer, tagHelper); + + @class.Children.Add(new CSharpStatementIRNode() + { + Content = writer.Builder.ToString(), + Parent = @class, + }); + } + + private void RewriteCreateNode( + NamespaceDeclarationIRNode @namespace, + ClassDeclarationIRNode @class, + CreateTagHelperIRNode node) + { + var originalTypeName = node.TagHelperTypeName; + + var newTypeName = GetVCTHFullName(@namespace, @class, node.Descriptor); + for (var i = 0; i < node.Parent.Children.Count; i++) + { + var setProperty = node.Parent.Children[i] as SetTagHelperPropertyIRNode; + if (setProperty != null) + { + setProperty.TagHelperTypeName = newTypeName; + } + } + + node.TagHelperTypeName = newTypeName; + } + + private static string GetVCTHFullName( + NamespaceDeclarationIRNode @namespace, + ClassDeclarationIRNode @class, + TagHelperDescriptor tagHelper) + { + var vcName = tagHelper.Metadata[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey]; + return $"{@namespace.Content}.{@class.Name}.__Generated__{vcName}ViewComponentTagHelper"; + } + + private static string GetVCTHClassName( + TagHelperDescriptor tagHelper) + { + var vcName = tagHelper.Metadata[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey]; + return $"__Generated__{vcName}ViewComponentTagHelper"; + } + + private void WriteClass(CSharpCodeWriter writer, TagHelperDescriptor descriptor) + { + // Add target element. + BuildTargetElementString(writer, descriptor); + + // Initialize declaration. + var tagHelperTypeName = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper"; + var className = GetVCTHClassName(descriptor); + + using (writer.BuildClassDeclaration("public", className, new[] { tagHelperTypeName })) + { + // Add view component helper. + writer.WriteVariableDeclaration( + $"private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper", + "_helper", + value: null); + + // Add constructor. + BuildConstructorString(writer, className); + + // Add attributes. + BuildAttributeDeclarations(writer, descriptor); + + // Add process method. + BuildProcessMethodString(writer, descriptor); + } + } + + private void BuildConstructorString(CSharpCodeWriter writer, string className) + { + var helperPair = new KeyValuePair( + $"global::Microsoft.AspNetCore.Mvc.IViewComponentHelper", + "helper"); + + using (writer.BuildConstructor("public", className, new[] { helperPair })) + { + writer.WriteStartAssignment("_helper") + .Write("helper") + .WriteLine(";"); + } + } + + private void BuildAttributeDeclarations(CSharpCodeWriter writer, TagHelperDescriptor descriptor) + { + writer.Write("[") + .Write("Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute") + .WriteParameterSeparator() + .Write($"global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute") + .WriteLine("]"); + + writer.WriteAutoPropertyDeclaration( + "public", + $"global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "ViewContext"); + + foreach (var attribute in descriptor.BoundAttributes) + { + writer.WriteAutoPropertyDeclaration( + "public", attribute.TypeName, attribute.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]); + + if (attribute.IndexerTypeName != null) + { + writer.Write(" = ") + .WriteStartNewObject(attribute.TypeName) + .WriteEndMethodInvocation(); + } + } + } + + private void BuildProcessMethodString(CSharpCodeWriter writer, TagHelperDescriptor descriptor) + { + var contextVariable = "context"; + var outputVariable = "output"; + + using (writer.BuildMethodDeclaration( + $"public override async", + $"global::{typeof(Task).FullName}", + "ProcessAsync", + new Dictionary() + { + { "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext", contextVariable }, + { "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput", outputVariable } + })) + { + writer.WriteInstanceMethodInvocation( + $"(_helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?", + "Contextualize", + new[] { "ViewContext" }); + + var methodParameters = GetMethodParameters(descriptor); + var contentVariable = "content"; + writer.Write("var ") + .WriteStartAssignment(contentVariable) + .WriteInstanceMethodInvocation($"await _helper", "InvokeAsync", methodParameters); + writer.WriteStartAssignment($"{outputVariable}.TagName") + .WriteLine("null;"); + writer.WriteInstanceMethodInvocation( + $"{outputVariable}.Content", + "SetHtmlContent", + new[] { contentVariable }); + } + } + + private string[] GetMethodParameters(TagHelperDescriptor descriptor) + { + var propertyNames = descriptor.BoundAttributes.Select( + attribute => attribute.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]); + var joinedPropertyNames = string.Join(", ", propertyNames); + var parametersString = $"new {{ { joinedPropertyNames } }}"; + + var viewComponentName = descriptor.Metadata[ + ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey]; + var methodParameters = new[] { $"\"{viewComponentName}\"", parametersString }; + return methodParameters; + } + + private void BuildTargetElementString(CSharpCodeWriter writer, TagHelperDescriptor descriptor) + { + Debug.Assert(descriptor.TagMatchingRules.Count() == 1); + + var rule = descriptor.TagMatchingRules.First(); + + writer.Write("[") + .WriteStartMethodInvocation("Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute") + .WriteStringLiteral(rule.TagName) + .WriteLine(")]"); + } + + private class Visitor : RazorIRNodeWalker + { + public ClassDeclarationIRNode Class { get; private set; } + + public DeclareTagHelperFieldsIRNode Fields { get; private set; } + + public NamespaceDeclarationIRNode Namespace { get; private set; } + + public List CreateTagHelpers { get; } = new List(); + + public Dictionary TagHelpers { get; } = new Dictionary(); + + public override void VisitCreateTagHelper(CreateTagHelperIRNode node) + { + var tagHelper = node.Descriptor; + if (ViewComponentTagHelperDescriptorConventions.IsViewComponentDescriptor(tagHelper)) + { + // Capture all the VCTagHelpers (unique by type name) so we can generate a class for each one. + var vcName = tagHelper.Metadata[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey]; + TagHelpers[vcName] = tagHelper; + + CreateTagHelpers.Add(node); + } + } + + public override void VisitNamespace(NamespaceDeclarationIRNode node) + { + if (Namespace == null) + { + Namespace = node; + } + + base.VisitNamespace(node); + } + + public override void VisitClass(ClassDeclarationIRNode node) + { + if (Class == null) + { + Class = node; + } + + base.VisitClass(node); + } + + public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node) + { + if (Fields == null) + { + Fields = node; + } + + base.VisitDeclareTagHelperFields(node); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DictionaryPrefixTestTagHelper.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DictionaryPrefixTestTagHelper.cs new file mode 100644 index 0000000000..7f0a41e197 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DictionaryPrefixTestTagHelper.cs @@ -0,0 +1,20 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + [HtmlTargetElement(Attributes = "prefix-*")] + public class DictionaryPrefixTestTagHelper : TagHelper + { + [HtmlAttributeName(DictionaryAttributePrefix = "prefix-")] + public IDictionary PrefixValues { get; set; } = new Dictionary(); + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InjectDirectiveTargetExtensionTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InjectDirectiveTargetExtensionTest.cs new file mode 100644 index 0000000000..0f4c5b84b9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InjectDirectiveTargetExtensionTest.cs @@ -0,0 +1,75 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.CodeGeneration; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class InjectDirectiveTargetExtensionTest + { + [Fact] + public void InjectDirectiveTargetExtension_WritesProperty() + { + // Arrange + var context = GetRenderingContext(); + var target = new InjectDirectiveTargetExtension(); + var node = new InjectDirectiveIRNode() + { + TypeName = "PropertyType", + MemberName = "PropertyName", + }; + + // Act + target.WriteInjectProperty(context, node); + + // Assert + Assert.Equal( + "[global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute]" + Environment.NewLine + + "public PropertyType PropertyName { get; private set; }" + Environment.NewLine, + context.Writer.Builder.ToString()); + } + + [Fact] + public void InjectDirectiveTargetExtension_WritesPropertyWithLinePragma_WhenSourceIsSet() + { + // Arrange + var context = GetRenderingContext(); + var target = new InjectDirectiveTargetExtension(); + var node = new InjectDirectiveIRNode() + { + TypeName = "PropertyType", + MemberName = "PropertyName", + Source = new SourceSpan( + filePath: "test-path", + absoluteIndex: 0, + lineIndex: 1, + characterIndex: 1, + length: 10) + }; + + // Act + target.WriteInjectProperty(context, node); + + // Assert + Assert.Equal( + "#line 2 \"test-path\"" + Environment.NewLine + + "[global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute]" + Environment.NewLine + + "public PropertyType PropertyName { get; private set; }" + Environment.NewLine + Environment.NewLine + + "#line default" + Environment.NewLine + + "#line hidden" + Environment.NewLine, + context.Writer.Builder.ToString()); + } + + private CSharpRenderingContext GetRenderingContext() + { + return new CSharpRenderingContext() + { + Writer = new CSharpCodeWriter() + }; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InjectDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InjectDirectiveTest.cs new file mode 100644 index 0000000000..8c86e1094a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InjectDirectiveTest.cs @@ -0,0 +1,225 @@ +// 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.Text; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class InjectDirectiveTest + { + [Fact] + public void InjectDirectivePass_Execute_DefinesProperty() + { + // Arrange + var codeDocument = CreateDocument(@" +@inject PropertyType PropertyName +"); + + var engine = CreateEngine(); + var pass = new InjectDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal(2, @class.Children.Count); + + var node = Assert.IsType(@class.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + } + + [Fact] + public void InjectDirectivePass_Execute_DedupesPropertiesByName() + { + // Arrange + var codeDocument = CreateDocument(@" +@inject PropertyType PropertyName +@inject PropertyType2 PropertyName +"); + + var engine = CreateEngine(); + var pass = new InjectDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal(2, @class.Children.Count); + + var node = Assert.IsType(@class.Children[1]); + Assert.Equal("PropertyType2", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + } + + [Fact] + public void InjectDirectivePass_Execute_ExpandsTModel_WithDynamic() + { + // Arrange + var codeDocument = CreateDocument(@" +@inject PropertyType PropertyName +"); + + var engine = CreateEngine(); + var pass = new InjectDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal(2, @class.Children.Count); + + var node = Assert.IsType(@class.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + } + + [Fact] + public void InjectDirectivePass_Execute_ExpandsTModel_WithModelTypeFirst() + { + // Arrange + var codeDocument = CreateDocument(@" +@model ModelType +@inject PropertyType PropertyName +"); + + var engine = CreateEngine(); + var pass = new InjectDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal(2, @class.Children.Count); + + var node = Assert.IsType(@class.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + } + + [Fact] + public void InjectDirectivePass_Execute_ExpandsTModel_WithModelType() + { + // Arrange + var codeDocument = CreateDocument(@" +@inject PropertyType PropertyName +@model ModelType +"); + + var engine = CreateEngine(); + var pass = new InjectDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal(2, @class.Children.Count); + + var node = Assert.IsType(@class.Children[1]); + Assert.Equal("PropertyType", node.TypeName); + Assert.Equal("PropertyName", node.MemberName); + } + + private RazorCodeDocument CreateDocument(string content) + { + var source = RazorSourceDocument.Create(content, "test.cshtml"); + return RazorCodeDocument.Create(source); + } + + private ClassDeclarationIRNode FindClassNode(RazorIRNode node) + { + var visitor = new ClassNodeVisitor(); + visitor.Visit(node); + return visitor.Node; + } + + private RazorEngine CreateEngine() + { + return RazorEngine.Create(b => + { + // Notice we're not registering the InjectDirective.Pass here so we can run it on demand. + b.AddDirective(InjectDirective.Directive); + b.AddDirective(ModelDirective.Directive); + }); + } + + private DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) + { + for (var i = 0; i < engine.Phases.Count; i++) + { + var phase = engine.Phases[i]; + phase.Execute(codeDocument); + + if (phase is IRazorDocumentClassifierPhase) + { + break; + } + } + + return codeDocument.GetIRDocument(); + } + + private string GetCSharpContent(RazorIRNode node) + { + var builder = new StringBuilder(); + for (var i = 0; i < node.Children.Count; i++) + { + var child = node.Children[i] as RazorIRToken; + if (child.Kind == RazorIRToken.TokenKind.CSharp) + { + builder.Append(child.Content); + } + } + + return builder.ToString(); + } + + private class ClassNodeVisitor : RazorIRNodeWalker + { + public ClassDeclarationIRNode Node { get; set; } + + public override void VisitClass(ClassDeclarationIRNode node) + { + Node = node; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InputTestTagHelper.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InputTestTagHelper.cs new file mode 100644 index 0000000000..d71a2b235f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InputTestTagHelper.cs @@ -0,0 +1,13 @@ +// 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 Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class InputTestTagHelper : TagHelper + { + public ModelExpression For { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/ModelExpression.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/ModelExpression.cs new file mode 100644 index 0000000000..6143c87352 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/ModelExpression.cs @@ -0,0 +1,10 @@ +// 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. + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + // This class is hacked up to appear like the one in MVC so that we don't reference MVC + public class ModelExpression + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/ResourceFile.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/ResourceFile.cs new file mode 100644 index 0000000000..ad7141f28a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/ResourceFile.cs @@ -0,0 +1,230 @@ +// 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 System.IO; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + /// + /// Reader and, if GENERATE_BASELINES is defined, writer for files compiled into an assembly as resources. + /// + /// Inspired by Razor's BaselineWriter and TestFile test classes. + public static class ResourceFile + { + private static object writeLock = new object(); + + /// + /// Return for from 's + /// manifest. + /// + /// The containing . + /// + /// Name of the manifest resource in . A path relative to the test project + /// directory. + /// + /// + /// If true is used as a source file and must exist. Otherwise + /// is an output file and, if GENERATE_BASELINES is defined, it will + /// soon be generated if missing. + /// + /// + /// for from 's + /// manifest. null if GENERATE_BASELINES is defined, is + /// false, and is not found in . + /// + /// + /// Thrown if GENERATE_BASELINES is not defined or is true and + /// is not found in . + /// + public static Stream GetResourceStream(Assembly assembly, string resourceName, bool sourceFile) + { + var fullName = $"{ assembly.GetName().Name }.{ resourceName.Replace('/', '.') }"; + if (!Exists(assembly, fullName)) + { +#if GENERATE_BASELINES + if (sourceFile) + { + // Even when generating baselines, a missing source file is a serious problem. + Assert.True(false, $"Manifest resource: { fullName } not found."); + } +#else + // When not generating baselines, a missing source or output file is always an error. + Assert.True(false, $"Manifest resource '{ fullName }' not found."); +#endif + + return null; + } + + var stream = assembly.GetManifestResourceStream(fullName); + if (sourceFile) + { + // Normalize line endings to '\r\n' (CRLF). This removes core.autocrlf, core.eol, core.safecrlf, and + // .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle + // some line endings like "\r" but otherwise ensures checksums and line mappings are consistent. + string text; + using (var streamReader = new StreamReader(stream)) + { + text = streamReader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n"); + } + + var bytes = Encoding.UTF8.GetBytes(text); + stream = new MemoryStream(bytes); + } + + return stream; + } + + /// + /// Return content of from 's + /// manifest. + /// + /// The containing . + /// + /// Name of the manifest resource in . A path relative to the test project + /// directory. + /// + /// + /// If true is used as a source file and must exist. Otherwise + /// is an output file and, if GENERATE_BASELINES is defined, it will + /// soon be generated if missing. + /// + /// + /// A which on completion returns the content of + /// from 's manifest. null if + /// GENERATE_BASELINES is defined, is false, and + /// is not found in . + /// + /// + /// Thrown if GENERATE_BASELINES is not defined or is true and + /// is not found in . + /// + /// Normalizes line endings to . + public static async Task ReadResourceAsync(Assembly assembly, string resourceName, bool sourceFile) + { + using (var stream = GetResourceStream(assembly, resourceName, sourceFile)) + { + if (stream == null) + { + return null; + } + + using (var streamReader = new StreamReader(stream)) + { + return await streamReader.ReadToEndAsync(); + } + } + } + + /// + /// Return content of from 's + /// manifest. + /// + /// The containing . + /// + /// Name of the manifest resource in . A path relative to the test project + /// directory. + /// + /// + /// If true is used as a source file and must exist. Otherwise + /// is an output file and, if GENERATE_BASELINES is defined, it will + /// soon be generated if missing. + /// + /// + /// The content of from 's + /// manifest. null if GENERATE_BASELINES is defined, is + /// false, and is not found in . + /// + /// + /// Thrown if GENERATE_BASELINES is not defined or is true and + /// is not found in . + /// + /// Normalizes line endings to . + public static string ReadResource(Assembly assembly, string resourceName, bool sourceFile) + { + using (var stream = GetResourceStream(assembly, resourceName, sourceFile)) + { + if (stream == null) + { + return null; + } + + using (var streamReader = new StreamReader(stream)) + { + return streamReader.ReadToEnd(); + } + } + } + + /// + /// Write to file that will become in + /// the next time the project is built. Does nothing if + /// and already match. + /// + /// The containing . + /// + /// Name of the manifest resource in . A path relative to the test project + /// directory. + /// + /// + /// Current content of . null if does + /// not currently exist in . + /// + /// + /// New content of in . + /// + [Conditional("GENERATE_BASELINES")] + public static void UpdateFile(Assembly assembly, string resourceName, string previousContent, string content) + { + // Normalize line endings to '\r\n' for comparison. This removes Environment.NewLine from the equation. Not + // worth updating files just because we generate baselines on a different system. + var normalizedPreviousContent = previousContent?.Replace("\r", "").Replace("\n", "\r\n"); + var normalizedContent = content.Replace("\r", "").Replace("\n", "\r\n"); + + if (!string.Equals(normalizedPreviousContent, normalizedContent, StringComparison.Ordinal)) + { + // The build system compiles every file under the resources folder as a resource available at runtime + // with the same name as the file name. Need to update this file on disc. + var projectPath = SolutionPathUtility.GetProjectPath("test", assembly); + var fullPath = Path.Combine(projectPath, resourceName); + WriteFile(fullPath, content); + } + } + + private static bool Exists(Assembly assembly, string fullName) + { + var resourceNames = assembly.GetManifestResourceNames(); + foreach (var resourceName in resourceNames) + { + // Resource names are case-sensitive. + if (string.Equals(fullName, resourceName, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + private static void WriteFile(string fullPath, string content) + { + // Serialize writes to minimize contention for file handles and directory access. + lock (writeLock) + { + // Write content to the file, creating it if necessary. + using (var stream = File.Open(fullPath, FileMode.Create, FileAccess.Write)) + { + using (var writer = new StreamWriter(stream)) + { + writer.Write(content); + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/SolutionPathUtility.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/SolutionPathUtility.cs new file mode 100644 index 0000000000..b8d9633080 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Internal/SolutionPathUtility.cs @@ -0,0 +1,45 @@ +// 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.IO; +using System.Reflection; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public static class SolutionPathUtility + { + private const string SolutionName = "Razor.sln"; + + /// + /// Gets the full path to the project. + /// + /// + /// The parent directory of the project. + /// e.g. samples, test, or test/Websites + /// + /// The project's assembly. + /// The full path to the project. + public static string GetProjectPath(string solutionRelativePath, Assembly assembly) + { + var projectName = assembly.GetName().Name; + var applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath; + + var directoryInfo = new DirectoryInfo(applicationBasePath); + do + { + var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, SolutionName)); + if (solutionFileInfo.Exists) + { + return Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath, projectName)); + } + + directoryInfo = directoryInfo.Parent; + } + while (directoryInfo.Parent != null); + + throw new Exception($"Solution root could not be located using application root {applicationBasePath}."); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/LineMappingsSerializer.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/LineMappingsSerializer.cs new file mode 100644 index 0000000000..0ae60facca --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/LineMappingsSerializer.cs @@ -0,0 +1,54 @@ +// 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.Text; +using Microsoft.AspNetCore.Razor.Evolution; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public static class LineMappingsSerializer + { + public static string Serialize(RazorCSharpDocument csharpDocument, RazorSourceDocument sourceDocument) + { + var builder = new StringBuilder(); + var sourceFileName = sourceDocument.FileName; + var charBuffer = new char[sourceDocument.Length]; + sourceDocument.CopyTo(0, charBuffer, 0, sourceDocument.Length); + var sourceContent = new string(charBuffer); + + for (var i = 0; i < csharpDocument.LineMappings.Count; i++) + { + var lineMapping = csharpDocument.LineMappings[i]; + if (!string.Equals(lineMapping.OriginalSpan.FilePath, sourceFileName, StringComparison.Ordinal)) + { + continue; + } + + builder.Append("Source Location: "); + AppendMappingLocation(builder, lineMapping.OriginalSpan, sourceContent); + + builder.Append("Generated Location: "); + AppendMappingLocation(builder, lineMapping.GeneratedSpan, csharpDocument.GeneratedCode); + + builder.AppendLine(); + } + + return builder.ToString(); + } + + private static void AppendMappingLocation(StringBuilder builder, SourceSpan location, string content) + { + builder + .AppendLine(location.ToString()) + .Append("|"); + + for (var i = 0; i < location.Length; i++) + { + builder.Append(content[location.AbsoluteIndex + i]); + } + + builder.AppendLine("|"); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test.csproj new file mode 100644 index 0000000000..f5cc5cb9de --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test.csproj @@ -0,0 +1,37 @@ + + + + + + netcoreapp1.1;net452 + netcoreapp1.1 + true + $(DefineConstants);GENERATE_BASELINES + $(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES + $(DefaultItemExcludes);TestFiles\** + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelDirectiveTest.cs new file mode 100644 index 0000000000..7bb66ded2c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelDirectiveTest.cs @@ -0,0 +1,220 @@ +// 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.IO; +using System.Text; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class ModelDirectiveTest + { + [Fact] + public void ModelDirective_GetModelType_GetsTypeFromLastWellFormedDirective() + { + // Arrange + var codeDocument = CreateDocument(@" +@model Type1 +@model Type2 +@model +"); + + var engine = CreateEngine(); + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + var result = ModelDirective.GetModelType(irDocument); + + // Assert + Assert.Equal("Type2", result); + } + + [Fact] + public void ModelDirective_GetModelType_DefaultsToDynamic() + { + // Arrange + var codeDocument = CreateDocument(@" "); + + var engine = CreateEngine(); + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + var result = ModelDirective.GetModelType(irDocument); + + // Assert + Assert.Equal("dynamic", result); + } + + [Fact] + public void ModelDirectivePass_Execute_ReplacesTModelInBaseType() + { + // Arrange + var codeDocument = CreateDocument(@" +@inherits BaseType +@model Type1 +"); + + var engine = CreateEngine(); + var pass = new ModelDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal("BaseType", @class.BaseType); + } + + [Fact] + public void ModelDirectivePass_Execute_ReplacesTModelInBaseType_DifferentOrdering() + { + // Arrange + var codeDocument = CreateDocument(@" +@model Type1 +@inherits BaseType +@model Type2 +"); + + var engine = CreateEngine(); + var pass = new ModelDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal("BaseType", @class.BaseType); + } + + [Fact] + public void ModelDirectivePass_Execute_NoOpWithoutTModel() + { + // Arrange + var codeDocument = CreateDocument(@" +@inherits BaseType +@model Type1 +"); + + var engine = CreateEngine(); + var pass = new ModelDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal("BaseType", @class.BaseType); + } + + [Fact] + public void ModelDirectivePass_Execute_ReplacesTModelInBaseType_DefaultDynamic() + { + // Arrange + var codeDocument = CreateDocument(@" +@inherits BaseType +"); + + var engine = CreateEngine(); + var pass = new ModelDirective.Pass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var @class = FindClassNode(irDocument); + Assert.NotNull(@class); + Assert.Equal("BaseType", @class.BaseType); + } + + private RazorCodeDocument CreateDocument(string content) + { + var source = RazorSourceDocument.Create(content, "test.cshtml"); + return RazorCodeDocument.Create(source); + } + + private ClassDeclarationIRNode FindClassNode(RazorIRNode node) + { + var visitor = new ClassNodeVisitor(); + visitor.Visit(node); + return visitor.Node; + } + + private RazorEngine CreateEngine() + { + return RazorEngine.Create(b => + { + // Notice we're not registering the ModelDirective.Pass here so we can run it on demand. + b.AddDirective(ModelDirective.Directive); + }); + } + + private DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) + { + for (var i = 0; i < engine.Phases.Count; i++) + { + var phase = engine.Phases[i]; + phase.Execute(codeDocument); + + if (phase is IRazorDocumentClassifierPhase) + { + break; + } + } + + return codeDocument.GetIRDocument(); + } + + private string GetCSharpContent(RazorIRNode node) + { + var builder = new StringBuilder(); + for (var i = 0; i < node.Children.Count; i++) + { + var child = node.Children[i] as RazorIRToken; + if (child.Kind == RazorIRToken.TokenKind.CSharp) + { + builder.Append(child.Content); + } + } + + return builder.ToString(); + } + + private class ClassNodeVisitor : RazorIRNodeWalker + { + public ClassDeclarationIRNode Node { get; set; } + + public override void VisitClass(ClassDeclarationIRNode node) + { + Node = node; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs new file mode 100644 index 0000000000..45853dcf27 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs @@ -0,0 +1,235 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class ModelExpressionPassTest + { + [Fact] + public void ModelExpressionPass_NonModelExpressionProperty_Ignored() + { + // Arrange + var codeDocument = CreateDocument(@" +@addTagHelper TestTagHelper, TestAssembly +

"); + + var tagHelpers = new[] + { + ITagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly") + .BindAttribute(attribute => + attribute + .Name("Foo") + .TypeName("System.Int32")) + .TagMatchingRule(rule => + rule.RequireTagName("p")) + .Build() + }; + + var engine = CreateEngine(tagHelpers); + var pass = new ModelExpressionPass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var tagHelper = FindTagHelperNode(irDocument); + var setProperty = tagHelper.Children.OfType().Single(); + + var child = Assert.IsType(Assert.Single(setProperty.Children)); + Assert.Equal("17", child.Content); + } + + [Fact] + public void ModelExpressionPass_ModelExpressionProperty_SimpleExpression() + { + // Arrange + + // Using \r\n here because we verify line mappings + var codeDocument = CreateDocument( + "@addTagHelper TestTagHelper, TestAssembly\r\n

"); + + var tagHelpers = new[] + { + ITagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly") + .BindAttribute(attribute => + attribute + .Name("Foo") + .TypeName(typeof(ModelExpression).FullName)) + .TagMatchingRule(rule => + rule.RequireTagName("p")) + .Build() + }; + + var engine = CreateEngine(tagHelpers); + var pass = new ModelExpressionPass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var tagHelper = FindTagHelperNode(irDocument); + var setProperty = tagHelper.Children.OfType().Single(); + + var expression = Assert.IsType(Assert.Single(setProperty.Children)); + Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => __model.Bar)", GetCSharpContent(expression)); + + var originalNode = Assert.IsType(expression.Children[2]); + Assert.Equal(RazorIRToken.TokenKind.CSharp, originalNode.Kind); + Assert.Equal("Bar", originalNode.Content); + Assert.Equal(new SourceSpan("test.cshtml", 51, 1, 8, 3), originalNode.Source.Value); + } + + [Fact] + public void ModelExpressionPass_ModelExpressionProperty_ComplexExpression() + { + // Arrange + + // Using \r\n here because we verify line mappings + var codeDocument = CreateDocument( + "@addTagHelper TestTagHelper, TestAssembly\r\n

"); + + var tagHelpers = new[] + { + ITagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly") + .BindAttribute(attribute => + attribute + .Name("Foo") + .TypeName(typeof(ModelExpression).FullName)) + .TagMatchingRule(rule => + rule.RequireTagName("p")) + .Build() + }; + + var engine = CreateEngine(tagHelpers); + var pass = new ModelExpressionPass() + { + Engine = engine, + }; + + var irDocument = CreateIRDocument(engine, codeDocument); + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + var tagHelper = FindTagHelperNode(irDocument); + var setProperty = tagHelper.Children.OfType().Single(); + + var expression = Assert.IsType(Assert.Single(setProperty.Children)); + Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => Bar)", GetCSharpContent(expression)); + + var originalNode = Assert.IsType(expression.Children[1]); + Assert.Equal(RazorIRToken.TokenKind.CSharp, originalNode.Kind); + Assert.Equal("Bar", originalNode.Content); + Assert.Equal(new SourceSpan("test.cshtml", 52, 1, 9, 3), originalNode.Source.Value); + } + + private RazorCodeDocument CreateDocument(string content) + { + var source = RazorSourceDocument.Create(content, "test.cshtml"); + return RazorCodeDocument.Create(source); + } + + private RazorEngine CreateEngine(params TagHelperDescriptor[] tagHelpers) + { + return RazorEngine.Create(b => + { + b.Features.Add(new TagHelperFeature(tagHelpers)); + }); + } + + private DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) + { + for (var i = 0; i < engine.Phases.Count; i++) + { + var phase = engine.Phases[i]; + phase.Execute(codeDocument); + + if (phase is IRazorDirectiveClassifierPhase) + { + break; + } + } + + return codeDocument.GetIRDocument(); + } + + private TagHelperIRNode FindTagHelperNode(RazorIRNode node) + { + var visitor = new TagHelperNodeVisitor(); + visitor.Visit(node); + return visitor.Node; + } + + private string GetCSharpContent(RazorIRNode node) + { + var builder = new StringBuilder(); + for (var i = 0; i < node.Children.Count; i++) + { + var child = node.Children[i] as RazorIRToken; + if (child.Kind == RazorIRToken.TokenKind.CSharp) + { + builder.Append(child.Content); + } + } + + return builder.ToString(); + } + + private class TagHelperNodeVisitor : RazorIRNodeWalker + { + public TagHelperIRNode Node { get; set; } + + public override void VisitTagHelper(TagHelperIRNode node) + { + Node = node; + } + } + + private class TagHelperFeature : ITagHelperFeature + { + public TagHelperFeature(TagHelperDescriptor[] tagHelpers) + { + Resolver = new TagHelperDescriptorResolver(tagHelpers); + } + + public RazorEngine Engine { get; set; } + + public ITagHelperDescriptorResolver Resolver { get; } + } + + private class TagHelperDescriptorResolver : ITagHelperDescriptorResolver + { + public TagHelperDescriptorResolver(TagHelperDescriptor[] tagHelpers) + { + TagHelpers = tagHelpers; + } + + public TagHelperDescriptor[] TagHelpers { get; } + + public IEnumerable Resolve(IList errors) + { + return TagHelpers; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcRazorTemplateEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcRazorTemplateEngineTest.cs new file mode 100644 index 0000000000..e14b83605e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcRazorTemplateEngineTest.cs @@ -0,0 +1,126 @@ +// 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.Linq; +using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.Extensions.FileProviders; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class MvcRazorTemplateEngineTest + { + [Fact] + public void GetDefaultImports_IncludesDefaultImports() + { + // Arrange + var expectedImports = new[] + { + "@using System", + "@using System.Linq", + "@using System.Collections.Generic", + "@using Microsoft.AspNetCore.Mvc", + "@using Microsoft.AspNetCore.Mvc.Rendering", + "@using Microsoft.AspNetCore.Mvc.ViewFeatures", + }; + var mvcRazorTemplateEngine = new MvcRazorTemplateEngine( + RazorEngine.Create(), + GetRazorProject(new TestFileProvider())); + + // Act + var imports = mvcRazorTemplateEngine.Options.DefaultImports; + + // Assert + var importContent = GetContent(imports) + .Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .Where(line => line.StartsWith("@using")); + Assert.Equal(expectedImports, importContent); + } + + [Fact] + public void GetDefaultImports_IncludesDefaulInjects() + { + // Arrange + var expectedImports = new[] + { + "@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html", + "@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json", + "@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component", + "@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url", + "@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider", + }; + var mvcRazorTemplateEngine = new MvcRazorTemplateEngine( + RazorEngine.Create(), + GetRazorProject(new TestFileProvider())); + + // Act + var imports = mvcRazorTemplateEngine.Options.DefaultImports; + + // Assert + var importContent = GetContent(imports) + .Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .Where(line => line.StartsWith("@inject")); + Assert.Equal(expectedImports, importContent); + } + + [Fact] + public void GetDefaultImports_IncludesUrlTagHelper() + { + // Arrange + var mvcRazorTemplateEngine = new MvcRazorTemplateEngine( + RazorEngine.Create(), + GetRazorProject(new TestFileProvider())); + + // Act + var imports = mvcRazorTemplateEngine.Options.DefaultImports; + + // Assert + var importContent = GetContent(imports) + .Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .Where(line => line.StartsWith("@addTagHelper")); + var addTagHelper = Assert.Single(importContent); + Assert.Equal("@addTagHelper Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor", + addTagHelper); + } + + [Fact] + public void CreateCodeDocument_SetsRelativePathOnOutput() + { + // Arrange + var path = "/Views/Home/Index.cshtml"; + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(path, "Hello world"); + var mvcRazorTemplateEngine = new MvcRazorTemplateEngine( + RazorEngine.Create(), + GetRazorProject(fileProvider)); + + // Act + var codeDocument = mvcRazorTemplateEngine.CreateCodeDocument(path); + + // Assert + Assert.Equal(path, codeDocument.GetRelativePath()); + } + + private string GetContent(RazorSourceDocument imports) + { + var contentChars = new char[imports.Length]; + imports.CopyTo(0, contentChars, 0, imports.Length); + return new string(contentChars); + } + + private static RazorProject GetRazorProject(IFileProvider fileProvider) + { + var razorProject = new Mock(); + razorProject.Setup(s => s.GetItem(It.IsAny())) + .Returns((string path) => { + var fileInfo = fileProvider.GetFileInfo(path); + return new DefaultRazorProjectItem(fileInfo, null, path); + }); + + return razorProject.Object; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcViewDocumentClassifierPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcViewDocumentClassifierPassTest.cs new file mode 100644 index 0000000000..da3bec1b26 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcViewDocumentClassifierPassTest.cs @@ -0,0 +1,244 @@ +// 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.IO; +using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class MvcViewDocumentClassifierPassTest + { + [Fact] + public void MvcViewDocumentClassifierPass_SetsDocumentKind() + { + // Arrange + var codeDocument = CreateDocument("some-content"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + Assert.Equal("mvc.1.0.view", irDocument.DocumentKind); + } + + [Fact] + public void MvcViewDocumentClassifierPass_NoOpsIfDocumentKindIsAlreadySet() + { + // Arrange + var codeDocument = CreateDocument("some-content"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + irDocument.DocumentKind = "some-value"; + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + Assert.Equal("some-value", irDocument.DocumentKind); + } + + [Fact] + public void MvcViewDocumentClassifierPass_SetsNamespace() + { + // Arrange + var codeDocument = CreateDocument("some-content"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal("AspNetCore", visitor.Namespace.Content); + } + + [Fact] + public void MvcViewDocumentClassifierPass_SetsClass() + { + // Arrange + var codeDocument = CreateDocument("some-content"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + codeDocument.SetRelativePath("Test.cshtml"); + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal("global::Microsoft.AspNetCore.Razor.RazorPage", visitor.Class.BaseType); + Assert.Equal("public", visitor.Class.AccessModifier); + Assert.Equal("Test_cshtml", visitor.Class.Name); + } + + [Theory] + [InlineData("/Views/Home/Index.cshtml", "_Views_Home_Index_cshtml")] + [InlineData("/Areas/MyArea/Views/Home/About.cshtml", "_Areas_MyArea_Views_Home_About_cshtml")] + public void MvcViewDocumentClassifierPass_UsesRelativePathToGenerateTypeName(string relativePath, string expected) + { + // Arrange + var codeDocument = CreateDocument("some-content"); + codeDocument.SetRelativePath(relativePath); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal(expected, visitor.Class.Name); + } + + [Fact] + public void MvcViewDocumentClassifierPass_UsesAbsolutePath_IfRelativePathIsNotSet() + { + // Arrange + var expected = "x___application_Views_Home_Index_cshtml"; + var path = @"x::\application\Views\Home\Index.cshtml"; + var codeDocument = CreateDocument("some-content", path); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal(expected, visitor.Class.Name); + } + + [Fact] + public void MvcViewDocumentClassifierPass_SanitizesClassName() + { + // Arrange + var expected = "path_with_invalid_chars"; + var codeDocument = CreateDocument("some-content"); + codeDocument.SetRelativePath("path.with+invalid-chars"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal(expected, visitor.Class.Name); + } + + [Fact] + public void MvcViewDocumentClassifierPass_SetsUpExecuteAsyncMethod() + { + // Arrange + var codeDocument = CreateDocument("some-content"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new MvcViewDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal("ExecuteAsync", visitor.Method.Name); + Assert.Equal("public", visitor.Method.AccessModifier); + Assert.Equal("global::System.Threading.Tasks.Task", visitor.Method.ReturnType); + Assert.Equal(new[] { "async", "override" }, visitor.Method.Modifiers); + } + + private static RazorCodeDocument CreateDocument(string content, string filePath = null) + { + filePath = filePath ?? Path.Combine(Directory.GetCurrentDirectory(), "Test.cshtml"); + + var source = RazorSourceDocument.Create(content, filePath); + return RazorCodeDocument.Create(source); + } + + private static RazorEngine CreateEngine() => RazorEngine.Create(); + + private static DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) + { + for (var i = 0; i < engine.Phases.Count; i++) + { + var phase = engine.Phases[i]; + phase.Execute(codeDocument); + + if (phase is IRazorIRLoweringPhase) + { + break; + } + } + + return codeDocument.GetIRDocument(); + } + + private class Visitor : RazorIRNodeWalker + { + public NamespaceDeclarationIRNode Namespace { get; private set; } + + public ClassDeclarationIRNode Class { get; private set; } + + public RazorMethodDeclarationIRNode Method { get; private set; } + + public override void VisitRazorMethodDeclaration(RazorMethodDeclarationIRNode node) + { + Method = node; + } + + public override void VisitNamespace(NamespaceDeclarationIRNode node) + { + Namespace = node; + base.VisitNamespace(node); + } + + public override void VisitClass(ClassDeclarationIRNode node) + { + Class = node; + base.VisitClass(node); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Properties/AssemblyInfo.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3337ebeac2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorEngineTest.cs new file mode 100644 index 0000000000..a698be121f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorEngineTest.cs @@ -0,0 +1,286 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Razor; +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.FileProviders; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class RazorEngineTest + { + private static Assembly _assembly = typeof(RazorEngineTest).GetTypeInfo().Assembly; + + #region Runtime + [Fact] + public void RazorEngine_Basic_Runtime() + { + RunRuntimeTest("Basic"); + } + + [Fact] + public void RazorEngine_ViewImports_Runtime() + { + RunRuntimeTest("_ViewImports"); + } + + [Fact] + public void RazorEngine_Inject_Runtime() + { + RunRuntimeTest("Inject"); + } + + [Fact] + public void RazorEngine_InjectWithModel_Runtime() + { + RunRuntimeTest("InjectWithModel"); + } + + [Fact] + public void RazorEngine_InjectWithSemicolon_Runtime() + { + RunRuntimeTest("InjectWithSemicolon"); + } + + [Fact] + public void RazorEngine_Model_Runtime() + { + RunRuntimeTest("Model"); + } + + [Fact] + public void RazorEngine_ModelExpressionTagHelper_Runtime() + { + RunRuntimeTest("ModelExpressionTagHelper"); + } + #endregion + + #region DesignTime + [Fact] + public void RazorEngine_Basic_DesignTime() + { + RunDesignTimeTest("Basic"); + } + + [Fact] + public void RazorEngine_ViewImports_DesignTime() + { + RunDesignTimeTest("_ViewImports"); + } + + [Fact] + public void RazorEngine_Inject_DesignTime() + { + RunDesignTimeTest("Inject"); + } + + [Fact] + public void RazorEngine_InjectWithModel_DesignTime() + { + RunDesignTimeTest("InjectWithModel"); + } + + [Fact] + public void RazorEngine_InjectWithSemicolon_DesignTime() + { + RunDesignTimeTest("InjectWithSemicolon"); + } + + [Fact] + public void RazorEngine_Model_DesignTime() + { + RunDesignTimeTest("Model"); + } + + [Fact] + public void RazorEngine_MultipleModels_DesignTime() + { + RunDesignTimeTest("MultipleModels"); + } + + [Fact] + public void RazorEngine_ModelExpressionTagHelper_DesignTime() + { + RunDesignTimeTest("ModelExpressionTagHelper"); + } + #endregion + + private static void RunRuntimeTest(string testName) + { + // Arrange + var inputFile = "TestFiles/Input/" + testName + ".cshtml"; + var outputFile = "TestFiles/Output/Runtime/" + testName + ".cs"; + var expectedCode = ResourceFile.ReadResource(_assembly, outputFile, sourceFile: false); + + var engine = RazorEngine.Create(b => + { + InjectDirective.Register(b); + ModelDirective.Register(b); + + b.AddTargetExtension(new InjectDirectiveTargetExtension()); + + b.Features.Add(new ModelExpressionPass()); + b.Features.Add(new MvcViewDocumentClassifierPass()); + b.Features.Add(new DefaultInstrumentationPass()); + + b.Features.Add(new DefaultTagHelperFeature()); + b.Features.Add(GetMetadataReferenceFeature()); + }); + + var inputContent = ResourceFile.ReadResource(_assembly, inputFile, sourceFile: true); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(inputFile, inputContent); + var fileInfo = fileProvider.GetFileInfo(inputFile); + var razorTemplateEngine = new MvcRazorTemplateEngine(engine, GetRazorProject(fileProvider)); + var razorProjectItem = new DefaultRazorProjectItem(fileInfo, basePath: null, path: inputFile); + var codeDocument = razorTemplateEngine.CreateCodeDocument(razorProjectItem); + codeDocument.Items["SuppressUniqueIds"] = "test"; + codeDocument.Items["NewLineString"] = "\r\n"; + + // Act + var csharpDocument = razorTemplateEngine.GenerateCode(codeDocument); + + // Assert + Assert.Empty(csharpDocument.Diagnostics); + +#if GENERATE_BASELINES + ResourceFile.UpdateFile(_assembly, outputFile, expectedCode, csharpDocument.GeneratedCode); +#else + Assert.Equal(expectedCode, csharpDocument.GeneratedCode, ignoreLineEndingDifferences: true); +#endif + } + + private static void RunDesignTimeTest(string testName) + { + // Arrange + var inputFile = "TestFiles/Input/" + testName + ".cshtml"; + var outputFile = "TestFiles/Output/DesignTime/" + testName + ".cs"; + var expectedCode = ResourceFile.ReadResource(_assembly, outputFile, sourceFile: false); + + var lineMappingOutputFile = "TestFiles/Output/DesignTime/" + testName + ".mappings.txt"; + var expectedMappings = ResourceFile.ReadResource(_assembly, lineMappingOutputFile, sourceFile: false); + + var engine = RazorEngine.CreateDesignTime(b => + { + InjectDirective.Register(b); + ModelDirective.Register(b); + + b.AddTargetExtension(new InjectDirectiveTargetExtension()); + + b.Features.Add(new ModelExpressionPass()); + b.Features.Add(new MvcViewDocumentClassifierPass()); + + b.Features.Add(new DefaultTagHelperFeature()); + b.Features.Add(GetMetadataReferenceFeature()); + }); + + var inputContent = ResourceFile.ReadResource(_assembly, inputFile, sourceFile: true); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(inputFile, inputContent); + var fileInfo = fileProvider.GetFileInfo(inputFile); + var razorTemplateEngine = new MvcRazorTemplateEngine(engine, GetRazorProject(fileProvider)); + var razorProjectItem = new DefaultRazorProjectItem(fileInfo, basePath: null, path: inputFile); + var codeDocument = razorTemplateEngine.CreateCodeDocument(razorProjectItem); + codeDocument.Items["SuppressUniqueIds"] = "test"; + codeDocument.Items["NewLineString"] = "\r\n"; + + // Act + var csharpDocument = razorTemplateEngine.GenerateCode(codeDocument); + + // Assert + Assert.Empty(csharpDocument.Diagnostics); + + var serializedMappings = LineMappingsSerializer.Serialize(csharpDocument, codeDocument.Source); + +#if GENERATE_BASELINES + ResourceFile.UpdateFile(_assembly, outputFile, expectedCode, csharpDocument.GeneratedCode); + ResourceFile.UpdateFile(_assembly, lineMappingOutputFile, expectedMappings, serializedMappings); +#else + Assert.Equal(expectedCode, csharpDocument.GeneratedCode, ignoreLineEndingDifferences: true); + Assert.Equal(expectedMappings, serializedMappings, ignoreLineEndingDifferences: true); +#endif + } + + private static IRazorEngineFeature GetMetadataReferenceFeature() + { + var currentAssembly = typeof(RazorEngineTest).GetTypeInfo().Assembly; + var dependencyContext = DependencyContext.Load(currentAssembly); + + var references = dependencyContext.CompileLibraries.SelectMany(l => l.ResolveReferencePaths()) + .Select(assemblyPath => MetadataReference.CreateFromFile(assemblyPath)) + .ToList(); + + var syntaxTree = CreateTagHelperSyntaxTree(); + var compilation = CSharpCompilation.Create("Microsoft.AspNetCore.Mvc.Razor", syntaxTree, references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var stream = new MemoryStream(); + var compilationResult = compilation.Emit(stream, options: new EmitOptions() ); + stream.Position = 0; + + Assert.True(compilationResult.Success); + + references.Add(MetadataReference.CreateFromStream(stream)); + + var feature = new DefaultMetadataReferenceFeature() + { + References = references, + }; + + return feature; + } + + private static IEnumerable CreateTagHelperSyntaxTree() + { + var text = $@" + public class UrlResolutionTagHelper : {typeof(TagHelper).FullName} + {{ + + }}"; + + return new SyntaxTree[] { CSharpSyntaxTree.ParseText(text) }; + } + + private static RazorProject GetRazorProject(IFileProvider fileProvider) + { + var razorProject = new Mock(); + + return razorProject.Object; + } + } + + public class DefaultRazorProjectItem : RazorProjectItem + { + public DefaultRazorProjectItem(IFileInfo fileInfo, string basePath, string path) + { + FileInfo = fileInfo; + BasePath = basePath; + Path = path; + } + + public IFileInfo FileInfo { get; } + + public override string BasePath { get; } + + public override string Path { get; } + + public override string PhysicalPath { get; } + + public override bool Exists => FileInfo.Exists; + + public override Stream Read() + { + return FileInfo.CreateReadStream(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs new file mode 100644 index 0000000000..364ab3378b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs @@ -0,0 +1,270 @@ +// 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.IO; +using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; +using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class RazorPageDocumentClassifierPassTest + { + [Fact] + public void RazorPageDocumentClassifierPass_SetsDocumentKind() + { + // Arrange + var codeDocument = CreateDocument("@page"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + Assert.Equal("mvc.1.0.razor-page", irDocument.DocumentKind); + } + + [Fact] + public void RazorPageDocumentClassifierPass_NoOpsIfDocumentKindIsAlreadySet() + { + // Arrange + var codeDocument = CreateDocument("@page"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + irDocument.DocumentKind = "some-value"; + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + Assert.Equal("some-value", irDocument.DocumentKind); + } + + [Fact] + public void RazorPageDocumentClassifierPass_NoOpsIfPageDirectiveIsMalformed() + { + // Arrange + var codeDocument = CreateDocument("@page+1"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + irDocument.DocumentKind = "some-value"; + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + + // Assert + Assert.Equal("some-value", irDocument.DocumentKind); + } + + [Fact] + public void RazorPageDocumentClassifierPass_SetsNamespace() + { + // Arrange + var codeDocument = CreateDocument("@page"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal("AspNetCore", visitor.Namespace.Content); + } + + [Fact] + public void RazorPageDocumentClassifierPass_SetsClass() + { + // Arrange + var codeDocument = CreateDocument("@page"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + codeDocument.SetRelativePath("Test.cshtml"); + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal("global::Microsoft.AspNetCore.RazorPages.Page", visitor.Class.BaseType); + Assert.Equal("public", visitor.Class.AccessModifier); + Assert.Equal("Test_cshtml", visitor.Class.Name); + } + + [Theory] + [InlineData("/Views/Home/Index.cshtml", "_Views_Home_Index_cshtml")] + [InlineData("/Areas/MyArea/Views/Home/About.cshtml", "_Areas_MyArea_Views_Home_About_cshtml")] + public void RazorPageDocumentClassifierPass_UsesRelativePathToGenerateTypeName(string relativePath, string expected) + { + // Arrange + var codeDocument = CreateDocument("@page"); + codeDocument.SetRelativePath(relativePath); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal(expected, visitor.Class.Name); + } + + [Fact] + public void RazorPageDocumentClassifierPass_UsesAbsolutePath_IfRelativePathIsNotSet() + { + // Arrange + var expected = "x___application_Views_Home_Index_cshtml"; + var path = @"x::\application\Views\Home\Index.cshtml"; + var codeDocument = CreateDocument("@page", path); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal(expected, visitor.Class.Name); + } + + [Fact] + public void RazorPageDocumentClassifierPass_SanitizesClassName() + { + // Arrange + var expected = "path_with_invalid_chars"; + var codeDocument = CreateDocument("@page"); + codeDocument.SetRelativePath("path.with+invalid-chars"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal(expected, visitor.Class.Name); + } + + [Fact] + public void RazorPageDocumentClassifierPass_SetsUpExecuteAsyncMethod() + { + // Arrange + var codeDocument = CreateDocument("@page"); + var engine = CreateEngine(); + var irDocument = CreateIRDocument(engine, codeDocument); + var pass = new RazorPageDocumentClassifierPass + { + Engine = engine + }; + + // Act + pass.Execute(codeDocument, irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + // Assert + Assert.Equal("ExecuteAsync", visitor.Method.Name); + Assert.Equal("public", visitor.Method.AccessModifier); + Assert.Equal("global::System.Threading.Tasks.Task", visitor.Method.ReturnType); + Assert.Equal(new[] { "async", "override" }, visitor.Method.Modifiers); + } + + private static RazorCodeDocument CreateDocument(string content, string filePath = null) + { + filePath = filePath ?? Path.Combine(Directory.GetCurrentDirectory(), "Test.cshtml"); + + var source = RazorSourceDocument.Create(content, filePath); + return RazorCodeDocument.Create(source); + } + + private static RazorEngine CreateEngine() + { + return RazorEngine.Create(b => + { + PageDirective.Register(b); + }); + } + + private static DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument) + { + for (var i = 0; i < engine.Phases.Count; i++) + { + var phase = engine.Phases[i]; + phase.Execute(codeDocument); + + if (phase is IRazorIRLoweringPhase) + { + break; + } + } + + return codeDocument.GetIRDocument(); + } + + private class Visitor : RazorIRNodeWalker + { + public NamespaceDeclarationIRNode Namespace { get; private set; } + + public ClassDeclarationIRNode Class { get; private set; } + + public RazorMethodDeclarationIRNode Method { get; private set; } + + public override void VisitRazorMethodDeclaration(RazorMethodDeclarationIRNode node) + { + Method = node; + } + + public override void VisitNamespace(NamespaceDeclarationIRNode node) + { + Namespace = node; + base.VisitNamespace(node); + } + + public override void VisitClass(ClassDeclarationIRNode node) + { + Class = node; + base.VisitClass(node); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFileProvider.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFileProvider.cs new file mode 100644 index 0000000000..462508ef66 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFileProvider.cs @@ -0,0 +1,186 @@ +// 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.Collections.Generic; +using System.IO; +using System.Text; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions +{ + public class TestFileChangeToken : IChangeToken + { + public bool HasChanged => throw new NotImplementedException(); + + public bool ActiveChangeCallbacks => throw new NotImplementedException(); + + public IDisposable RegisterChangeCallback(Action callback, object state) + { + throw new NotImplementedException(); + } + } + + public class TestFileInfo : IFileInfo + { + private string _content; + + public bool IsDirectory { get; } = false; + + public DateTimeOffset LastModified { get; set; } + + public long Length { get; set; } + + public string Name { get; set; } + + public string PhysicalPath { get; set; } + + public string Content + { + get { return _content; } + set + { + _content = value; + Length = Encoding.UTF8.GetByteCount(Content); + } + } + + public bool Exists + { + get { return true; } + } + + public Stream CreateReadStream() + { + var bytes = Encoding.UTF8.GetBytes(Content); + return new MemoryStream(bytes); + } + } + + public class TestFileProvider : IFileProvider + { + private readonly Dictionary _lookup = + new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _directoryContentsLookup = + new Dictionary(); + + private readonly Dictionary _fileTriggers = + new Dictionary(StringComparer.Ordinal); + + public virtual IDirectoryContents GetDirectoryContents(string subpath) + { + if (_directoryContentsLookup.TryGetValue(subpath, out var value)) + { + return value; + } + + return new NotFoundDirectoryContents(); + } + + public TestFileInfo AddFile(string path, string contents) + { + var fileInfo = new TestFileInfo + { + Content = contents, + PhysicalPath = path, + Name = Path.GetFileName(path), + LastModified = DateTime.UtcNow, + }; + + AddFile(path, fileInfo); + + return fileInfo; + } + + public void AddFile(string path, IFileInfo contents) + { + _lookup[path] = contents; + } + + public void DeleteFile(string path) + { + _lookup.Remove(path); + } + + public virtual IFileInfo GetFileInfo(string subpath) + { + if (_lookup.ContainsKey(subpath)) + { + return _lookup[subpath]; + } + else + { + return new NotFoundFileInfo(); + } + } + + public virtual IChangeToken Watch(string filter) + { + TestFileChangeToken changeToken; + if (!_fileTriggers.TryGetValue(filter, out changeToken) || changeToken.HasChanged) + { + changeToken = new TestFileChangeToken(); + _fileTriggers[filter] = changeToken; + } + + return changeToken; + } + + private class NotFoundFileInfo : IFileInfo + { + public bool Exists + { + get + { + return false; + } + } + + public bool IsDirectory + { + get + { + throw new NotImplementedException(); + } + } + + public DateTimeOffset LastModified + { + get + { + throw new NotImplementedException(); + } + } + + public long Length + { + get + { + throw new NotImplementedException(); + } + } + + public string Name + { + get + { + throw new NotImplementedException(); + } + } + + public string PhysicalPath + { + get + { + throw new NotImplementedException(); + } + } + + public Stream CreateReadStream() + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Basic.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Basic.cshtml new file mode 100644 index 0000000000..90b91c5da6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Basic.cshtml @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Inject.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Inject.cshtml new file mode 100644 index 0000000000..4e90b36808 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Inject.cshtml @@ -0,0 +1,2 @@ +@using MyNamespace +@inject MyApp MyPropertyName \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/InjectWithModel.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/InjectWithModel.cshtml new file mode 100644 index 0000000000..b4d4a5589a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/InjectWithModel.cshtml @@ -0,0 +1,3 @@ +@model MyModel +@inject MyApp MyPropertyName +@inject MyService Html \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/InjectWithSemicolon.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/InjectWithSemicolon.cshtml new file mode 100644 index 0000000000..d840486787 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/InjectWithSemicolon.cshtml @@ -0,0 +1,5 @@ +@model MyModel +@inject MyApp MyPropertyName; +@inject MyService Html; +@inject MyApp MyPropertyName2 ; +@inject MyService Html2 ; \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Model.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Model.cshtml new file mode 100644 index 0000000000..4b73b2dc53 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/Model.cshtml @@ -0,0 +1 @@ +@model System.Collections.IEnumerable diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/ModelExpressionTagHelper.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/ModelExpressionTagHelper.cshtml new file mode 100644 index 0000000000..920e83919d --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/ModelExpressionTagHelper.cshtml @@ -0,0 +1,6 @@ +@model DateTime + +@addTagHelper Microsoft.AspNetCore.Mvc.Razor.Extensions.InputTestTagHelper, Microsoft.AspNetCore.Mvc.Razor.Extensions.Test + + + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/MultipleModels.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/MultipleModels.cshtml new file mode 100644 index 0000000000..350f93b776 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/MultipleModels.cshtml @@ -0,0 +1,2 @@ +@model ThisShouldBeGenerated +@model System.Collections.IEnumerable diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/_ViewImports.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/_ViewImports.cshtml new file mode 100644 index 0000000000..fea9533d09 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Input/_ViewImports.cshtml @@ -0,0 +1 @@ +@inject IHtmlHelper Model \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Basic.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Basic.cs new file mode 100644 index 0000000000..109e1c47fd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Basic.cs @@ -0,0 +1,108 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input_Basic_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { +#line 1 "TestFiles/Input/Basic.cshtml" + __o = logo; + +#line default +#line hidden +#line 3 "TestFiles/Input/Basic.cshtml" +__o = Html.Input("SomeKey"); + +#line default +#line hidden + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Basic.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Basic.mappings.txt new file mode 100644 index 0000000000..4ce32aa6b7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Basic.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (13:0,13 [4] TestFiles/Input/Basic.cshtml) +|logo| +Generated Location: (2321:85,13 [4] ) +|logo| + +Source Location: (43:2,5 [21] TestFiles/Input/Basic.cshtml) +|Html.Input("SomeKey")| +Generated Location: (2405:90,6 [21] ) +|Html.Input("SomeKey")| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Inject.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Inject.cs new file mode 100644 index 0000000000..be17e52a49 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Inject.cs @@ -0,0 +1,113 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden +#line 1 "TestFiles/Input/Inject.cshtml" +using MyNamespace; + +#line default +#line hidden + public class TestFiles_Input_Inject_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + ((System.Action)(() => { +MyApp __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object MyPropertyName = null; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public MyApp MyPropertyName { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Inject.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Inject.mappings.txt new file mode 100644 index 0000000000..33fe2afa6e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Inject.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (28:1,8 [5] TestFiles/Input/Inject.cshtml) +|MyApp| +Generated Location: (2166:84,0 [5] ) +|MyApp| + +Source Location: (34:1,14 [14] TestFiles/Input/Inject.cshtml) +|MyPropertyName| +Generated Location: (2268:88,14 [14] ) +|MyPropertyName| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithModel.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithModel.cs new file mode 100644 index 0000000000..13ea65b0c8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithModel.cs @@ -0,0 +1,120 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input_InjectWithModel_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + ((System.Action)(() => { +MyModel __typeHelper = null; + } + ))(); + ((System.Action)(() => { +MyApp __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object MyPropertyName = null; + } + ))(); + ((System.Action)(() => { +MyService __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public MyService Html { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public MyApp MyPropertyName { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithModel.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithModel.mappings.txt new file mode 100644 index 0000000000..603f8a6555 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithModel.mappings.txt @@ -0,0 +1,25 @@ +Source Location: (7:0,7 [7] TestFiles/Input/InjectWithModel.cshtml) +|MyModel| +Generated Location: (2083:79,0 [7] ) +|MyModel| + +Source Location: (24:1,8 [5] TestFiles/Input/InjectWithModel.cshtml) +|MyApp| +Generated Location: (2173:83,0 [5] ) +|MyApp| + +Source Location: (30:1,14 [14] TestFiles/Input/InjectWithModel.cshtml) +|MyPropertyName| +Generated Location: (2275:87,14 [14] ) +|MyPropertyName| + +Source Location: (54:2,8 [17] TestFiles/Input/InjectWithModel.cshtml) +|MyService| +Generated Location: (2359:91,0 [17] ) +|MyService| + +Source Location: (72:2,26 [4] TestFiles/Input/InjectWithModel.cshtml) +|Html| +Generated Location: (2473:95,14 [4] ) +|Html| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs new file mode 100644 index 0000000000..7aa3da5be5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs @@ -0,0 +1,140 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input_InjectWithSemicolon_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + ((System.Action)(() => { +MyModel __typeHelper = null; + } + ))(); + ((System.Action)(() => { +MyApp __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object MyPropertyName = null; + } + ))(); + ((System.Action)(() => { +MyService __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +MyApp __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object MyPropertyName2 = null; + } + ))(); + ((System.Action)(() => { +MyService __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html2 = null; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public MyService Html2 { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public MyApp MyPropertyName2 { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public MyService Html { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public MyApp MyPropertyName { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.mappings.txt new file mode 100644 index 0000000000..411adc17c5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.mappings.txt @@ -0,0 +1,45 @@ +Source Location: (7:0,7 [7] TestFiles/Input/InjectWithSemicolon.cshtml) +|MyModel| +Generated Location: (2087:79,0 [7] ) +|MyModel| + +Source Location: (24:1,8 [5] TestFiles/Input/InjectWithSemicolon.cshtml) +|MyApp| +Generated Location: (2177:83,0 [5] ) +|MyApp| + +Source Location: (30:1,14 [14] TestFiles/Input/InjectWithSemicolon.cshtml) +|MyPropertyName| +Generated Location: (2279:87,14 [14] ) +|MyPropertyName| + +Source Location: (58:2,8 [17] TestFiles/Input/InjectWithSemicolon.cshtml) +|MyService| +Generated Location: (2363:91,0 [17] ) +|MyService| + +Source Location: (76:2,26 [4] TestFiles/Input/InjectWithSemicolon.cshtml) +|Html| +Generated Location: (2477:95,14 [4] ) +|Html| + +Source Location: (93:3,8 [5] TestFiles/Input/InjectWithSemicolon.cshtml) +|MyApp| +Generated Location: (2551:99,0 [5] ) +|MyApp| + +Source Location: (99:3,14 [15] TestFiles/Input/InjectWithSemicolon.cshtml) +|MyPropertyName2| +Generated Location: (2653:103,14 [15] ) +|MyPropertyName2| + +Source Location: (129:4,8 [17] TestFiles/Input/InjectWithSemicolon.cshtml) +|MyService| +Generated Location: (2738:107,0 [17] ) +|MyService| + +Source Location: (147:4,26 [5] TestFiles/Input/InjectWithSemicolon.cshtml) +|Html2| +Generated Location: (2852:111,14 [5] ) +|Html2| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Model.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Model.cs new file mode 100644 index 0000000000..2f54843269 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Model.cs @@ -0,0 +1,102 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input_Model_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + ((System.Action)(() => { +System.Collections.IEnumerable __typeHelper = null; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Model.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Model.mappings.txt new file mode 100644 index 0000000000..577cdb0456 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/Model.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (7:0,7 [30] TestFiles/Input/Model.cshtml) +|System.Collections.IEnumerable| +Generated Location: (2096:79,0 [30] ) +|System.Collections.IEnumerable| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.cs new file mode 100644 index 0000000000..57e771afb9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.cs @@ -0,0 +1,119 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input_ModelExpressionTagHelper_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + ((System.Action)(() => { +DateTime __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Mvc.Razor.Extensions.InputTestTagHelper, Microsoft.AspNetCore.Mvc.Razor.Extensions.Test"; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + private global::Microsoft.AspNetCore.Mvc.Razor.Extensions.InputTestTagHelper __Microsoft_AspNetCore_Mvc_Razor_Extensions_InputTestTagHelper = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + __Microsoft_AspNetCore_Mvc_Razor_Extensions_InputTestTagHelper = CreateTagHelper(); +#line 5 "TestFiles/Input/ModelExpressionTagHelper.cshtml" +__Microsoft_AspNetCore_Mvc_Razor_Extensions_InputTestTagHelper.For = ModelExpressionProvider.CreateModelExpression(ViewData, __model => __model.Now); + +#line default +#line hidden + __Microsoft_AspNetCore_Mvc_Razor_Extensions_InputTestTagHelper = CreateTagHelper(); +#line 6 "TestFiles/Input/ModelExpressionTagHelper.cshtml" +__Microsoft_AspNetCore_Mvc_Razor_Extensions_InputTestTagHelper.For = ModelExpressionProvider.CreateModelExpression(ViewData, __model => Model); + +#line default +#line hidden + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.mappings.txt new file mode 100644 index 0000000000..d4320443cc --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.mappings.txt @@ -0,0 +1,20 @@ +Source Location: (7:0,7 [8] TestFiles/Input/ModelExpressionTagHelper.cshtml) +|DateTime| +Generated Location: (2093:79,0 [8] ) +|DateTime| + +Source Location: (33:2,14 [108] TestFiles/Input/ModelExpressionTagHelper.cshtml) +|Microsoft.AspNetCore.Mvc.Razor.Extensions.InputTestTagHelper, Microsoft.AspNetCore.Mvc.Razor.Extensions.Test| +Generated Location: (2214:83,30 [108] ) +|Microsoft.AspNetCore.Mvc.Razor.Extensions.InputTestTagHelper, Microsoft.AspNetCore.Mvc.Razor.Extensions.Test| + +Source Location: (162:4,17 [3] TestFiles/Input/ModelExpressionTagHelper.cshtml) +|Now| +Generated Location: (3108:95,144 [3] ) +|Now| + +Source Location: (189:5,18 [5] TestFiles/Input/ModelExpressionTagHelper.cshtml) +|Model| +Generated Location: (3508:101,136 [5] ) +|Model| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/MultipleModels.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/MultipleModels.cs new file mode 100644 index 0000000000..f471cbeed8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/MultipleModels.cs @@ -0,0 +1,106 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input_MultipleModels_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + ((System.Action)(() => { +ThisShouldBeGenerated __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Collections.IEnumerable __typeHelper = null; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/MultipleModels.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/MultipleModels.mappings.txt new file mode 100644 index 0000000000..2b2d90c7e3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/MultipleModels.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (7:0,7 [21] TestFiles/Input/MultipleModels.cshtml) +|ThisShouldBeGenerated| +Generated Location: (2105:79,0 [21] ) +|ThisShouldBeGenerated| + +Source Location: (37:1,7 [30] TestFiles/Input/MultipleModels.cshtml) +|System.Collections.IEnumerable| +Generated Location: (2209:83,0 [30] ) +|System.Collections.IEnumerable| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/_ViewImports.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/_ViewImports.cs new file mode 100644 index 0000000000..7b76a62f6a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/_ViewImports.cs @@ -0,0 +1,108 @@ +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input__ViewImports_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Html = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Json = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Component = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.IUrlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Url = null; + } + ))(); + ((System.Action)(() => { +global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object ModelExpressionProvider = null; + } + ))(); + ((System.Action)(() => { +System.Object __typeHelper = "Microsoft.AspNetCore.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor"; + } + ))(); + ((System.Action)(() => { +IHtmlHelper __typeHelper = null; + } + ))(); + ((System.Action)(() => { +System.Object Model = null; + } + ))(); + } + #pragma warning restore 219 + private static System.Object __o = null; + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + } + #pragma warning restore 1998 + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public IHtmlHelper Model { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } + [global::Microsoft.AspNetCore.Razor.Internal.RazorInjectAttribute] + public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/_ViewImports.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/_ViewImports.mappings.txt new file mode 100644 index 0000000000..1085a35a8b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/DesignTime/_ViewImports.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (8:0,8 [19] TestFiles/Input/_ViewImports.cshtml) +|IHtmlHelper| +Generated Location: (2080:79,0 [19] ) +|IHtmlHelper| + +Source Location: (28:0,28 [5] TestFiles/Input/_ViewImports.cshtml) +|Model| +Generated Location: (2196:83,14 [5] ) +|Model| + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/Runtime/Basic.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/Runtime/Basic.cs new file mode 100644 index 0000000000..4aa259491c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/Output/Runtime/Basic.cs @@ -0,0 +1,73 @@ +#pragma checksum "TestFiles/Input/Basic.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "54a70ff4c6d27ac6cdc6725cb6bab12012015729" +namespace AspNetCore +{ + #line hidden + using System; + using System.Threading.Tasks; +#line 2 "" +using System.Linq; + +#line default +#line hidden +#line 3 "" +using System.Collections.Generic; + +#line default +#line hidden +#line 4 "" +using Microsoft.AspNetCore.Mvc; + +#line default +#line hidden +#line 5 "" +using Microsoft.AspNetCore.Mvc.Rendering; + +#line default +#line hidden +#line 6 "" +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +#line default +#line hidden + public class TestFiles_Input_Basic_cshtml : global::Microsoft.AspNetCore.Razor.RazorPage + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + BeginContext(0, 4, true); + WriteLiteral("