diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/BaseDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/BaseDocumentClassifierPass.cs deleted file mode 100644 index 7bfb8bb393..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Host/BaseDocumentClassifierPass.cs +++ /dev/null @@ -1,177 +0,0 @@ -// 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.Globalization; -using System.IO; -using Microsoft.AspNetCore.Razor.Evolution; -using Microsoft.AspNetCore.Razor.Evolution.Intermediate; -using Microsoft.Extensions.Primitives; - -namespace Microsoft.AspNetCore.Mvc.Razor.Host -{ - public abstract class BaseDocumentClassifierPass : IRazorIRPass - { - public RazorEngine Engine { get; set; } - - // We want to run before the default, but after others since this is the MVC default. - public virtual int Order => RazorIRPass.DefaultDocumentClassifierOrder - 1; - - protected abstract string BaseType { get; } - - public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument) - { - if (irDocument.DocumentKind != null) - { - return irDocument; - } - - var documentKind = ClassifyDocument(codeDocument, irDocument); - if (documentKind == null) - { - return irDocument; - } - - irDocument.DocumentKind = documentKind; - - return ExecuteCore(codeDocument, irDocument); - } - - protected virtual DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) - { - // Rewrite a use default namespace and class declaration. - var children = new List(irDocument.Children); - irDocument.Children.Clear(); - - var @namespace = new NamespaceDeclarationIRNode - { - Content = "AspNetCore", - }; - - var @class = new ClassDeclarationIRNode - { - AccessModifier = "public", - Name = GetClassName(codeDocument.Source.Filename) ?? "GeneratedClass", - BaseType = BaseType, - }; - - var method = new RazorMethodDeclarationIRNode() - { - AccessModifier = "public", - Modifiers = new List() { "async", "override" }, - Name = "ExecuteAsync", - ReturnType = "Task", - }; - - var documentBuilder = RazorIRBuilder.Create(irDocument); - - var namespaceBuilder = RazorIRBuilder.Create(documentBuilder.Current); - namespaceBuilder.Push(@namespace); - - var classBuilder = RazorIRBuilder.Create(namespaceBuilder.Current); - classBuilder.Push(@class); - - var methodBuilder = RazorIRBuilder.Create(classBuilder.Current); - methodBuilder.Push(method); - - var visitor = new Visitor(documentBuilder, namespaceBuilder, classBuilder, methodBuilder); - - for (var i = 0; i < children.Count; i++) - { - visitor.Visit(children[i]); - } - - return irDocument; - } - - protected abstract string ClassifyDocument(RazorCodeDocument codeDocument, DocumentIRNode irDocument); - - private static string GetClassName(string filename) - { - if (filename == null) - { - return null; - } - - return SanitizeClassName("Generated_" + Path.GetFileNameWithoutExtension(filename)); - } - - // 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 - } - - public 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(); - } - - private class Visitor : RazorIRNodeVisitor - { - private readonly RazorIRBuilder _document; - private readonly RazorIRBuilder _namespace; - private readonly RazorIRBuilder _class; - private readonly RazorIRBuilder _method; - - public Visitor(RazorIRBuilder document, RazorIRBuilder @namespace, RazorIRBuilder @class, RazorIRBuilder method) - { - _document = document; - _namespace = @namespace; - _class = @class; - _method = method; - } - - public override void VisitChecksum(ChecksumIRNode node) - { - _document.Insert(0, node); - } - - public override void VisitUsingStatement(UsingStatementIRNode node) - { - _namespace.AddAfter(node); - } - - public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node) - { - _class.Insert(0, node); - } - - public override void VisitDefault(RazorIRNode node) - { - _method.Add(node); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/ClassName.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/ClassName.cs new file mode 100644 index 0000000000..799091569d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/Internal/ClassName.cs @@ -0,0 +1,60 @@ +using System.Globalization; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Razor.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.Host/ModelDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/ModelDirective.cs index 56d8e9b61b..9e0a2f7184 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Host/ModelDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/ModelDirective.cs @@ -42,11 +42,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host var tokens = directive.Tokens.ToArray(); if (tokens.Length >= 1) { + document.Parent = directive; return tokens[0].Content; } } - if (document.DocumentKind == RazorPageDocumentClassifier.DocumentKind) + if (document.DocumentKind == RazorPageDocumentClassifier.RazorPageDocumentKind) { return visitor.Class.Name; } @@ -94,6 +95,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host public IList ModelDirectives { get; } = new List(); + public override void VisitDocument(DocumentIRNode node) + { + if (node.Parent != null) + { + ModelDirectives.Add((DirectiveIRNode)node.Parent); + } + base.VisitDocument(node); + } + public override void VisitClass(ClassDeclarationIRNode node) { if (Class == null) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcViewDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcViewDocumentClassifierPass.cs index 792de7eea3..484f31b70e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcViewDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/MvcViewDocumentClassifierPass.cs @@ -1,18 +1,35 @@ // 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.Internal; using Microsoft.AspNetCore.Razor.Evolution; using Microsoft.AspNetCore.Razor.Evolution.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Host { - public class MvcViewDocumentClassifierPass : BaseDocumentClassifierPass + public class MvcViewDocumentClassifierPass : DocumentClassifierPassBase { - public static readonly string DocumentKind = "mvc.1.0.view"; + public readonly string MvcViewDocumentKind = "mvc.1.0.view"; - protected override string BaseType => "Microsoft.AspNetCore.Mvc.Razor.RazorPage"; + protected override string DocumentKind => MvcViewDocumentKind; - protected override string ClassifyDocument(RazorCodeDocument codeDocument, DocumentIRNode irDocument) - => DocumentKind; + protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument) => true; + + protected override void OnDocumentStructureCreated( + RazorCodeDocument codeDocument, + NamespaceDeclarationIRNode @namespace, + ClassDeclarationIRNode @class, + RazorMethodDeclarationIRNode method) + { + base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); + @class.Name = ClassName.GetClassNameFromPath(codeDocument.Source.Filename); + @class.BaseType = "Microsoft.AspNetCore.Mvc.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.Host/PagesPropertyInjectionPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/PagesPropertyInjectionPass.cs index eb0d45e63d..a51326dcad 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Host/PagesPropertyInjectionPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/PagesPropertyInjectionPass.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument) { - if (irDocument.DocumentKind != RazorPageDocumentClassifier.DocumentKind) + if (irDocument.DocumentKind != RazorPageDocumentClassifier.RazorPageDocumentKind) { return irDocument; } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/RazorPageDocumentClassifier.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/RazorPageDocumentClassifier.cs index c521bc05fc..70a7447003 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Host/RazorPageDocumentClassifier.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/RazorPageDocumentClassifier.cs @@ -1,26 +1,35 @@ // 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.Internal; using Microsoft.AspNetCore.Razor.Evolution; using Microsoft.AspNetCore.Razor.Evolution.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Host { - public class RazorPageDocumentClassifier : BaseDocumentClassifierPass + public class RazorPageDocumentClassifier : DocumentClassifierPassBase { - public static readonly string DocumentKind = "mvc.1.0.razor-page"; + public static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page"; - protected override string BaseType => "Microsoft.AspNetCore.Mvc.RazorPages.Page"; + protected override string DocumentKind => RazorPageDocumentKind; - protected override string ClassifyDocument(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument) { string routePrefix; - if (PageDirective.TryGetRouteTemplate(irDocument, out routePrefix)) - { - return DocumentKind; - } + return PageDirective.TryGetRouteTemplate(irDocument, out routePrefix); + } - return null; + protected override void OnDocumentStructureCreated(RazorCodeDocument codeDocument, NamespaceDeclarationIRNode @namespace, ClassDeclarationIRNode @class, RazorMethodDeclarationIRNode method) + { + base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); + @class.BaseType = "Microsoft.AspNetCore.Mvc.RazorPages.Page"; + @class.Name = ClassName.GetClassNameFromPath(codeDocument.Source.Filename); + @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/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs index 24b4d478f3..cb076afc5c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs @@ -1518,7 +1518,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor { get { - var bufferedWriter = Assert.IsType(Writer); + var bufferedWriter = Assert.IsType(Output); using (var stringWriter = new StringWriter()) { bufferedWriter.Buffer.WriteTo(stringWriter, HtmlEncoder);