| File: Mvc\RazorPageDocumentClassifierPass.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; public sealed class RazorPageDocumentClassifierPass : DocumentClassifierPassBase { private readonly bool _useConsolidatedMvcViews; public static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page"; public static readonly string RouteTemplateKey = "RouteTemplate"; public RazorPageDocumentClassifierPass() : this(false) { } public RazorPageDocumentClassifierPass(bool useConsolidatedMvcViews) { _useConsolidatedMvcViews = useConsolidatedMvcViews; } private static readonly RazorProjectEngine LeadingDirectiveParsingEngine = RazorProjectEngine.Create( new(RazorLanguageVersion.Version_3_0, "leading-directive-parser", []), RazorProjectFileSystem.Create("/"), builder => { for (var i = builder.Phases.Count - 1; i >= 0; i--) { var phase = builder.Phases[i]; builder.Phases.RemoveAt(i); if (phase is IRazorDocumentClassifierPhase) { break; } } RazorExtensions.Register(builder); builder.ConfigureParserOptions(builder => { builder.ParseLeadingDirectives = true; }); }); protected override string DocumentKind => RazorPageDocumentKind; protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { return PageDirective.TryGetPageDirective(documentNode, out _); } protected override void OnDocumentStructureCreated( RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); if (!codeDocument.TryGetNamespace(fallbackToRootNamespace: false, out var namespaceName)) { @namespace.Name = _useConsolidatedMvcViews ? "AspNetCoreGeneratedDocument" : "AspNetCore"; } else { @namespace.Name = namespaceName; } if (!codeDocument.TryComputeClassName(out var className)) { // It's possible for a Razor document to not have a file path. // Eg. When we try to generate code for an in memory document like default imports. var checksum = ChecksumUtilities.BytesToString(codeDocument.Source.Text.GetChecksum()); @class.Name = $"AspNetCore_{checksum}"; } else { @class.Name = className; } @class.BaseType = new BaseTypeWithModel("global::Microsoft.AspNetCore.Mvc.RazorPages.Page"); @class.Modifiers = _useConsolidatedMvcViews ? CommonModifiers.InternalSealed : CommonModifiers.Public; @class.NullableContext = true; method.Name = "ExecuteAsync"; method.Modifiers = CommonModifiers.PublicAsyncOverride; method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}"; var document = codeDocument.GetRequiredDocumentNode(); PageDirective.TryGetPageDirective(document, out var pageDirective); EnsureValidPageDirective(codeDocument, pageDirective); AddRouteTemplateMetadataAttribute(@namespace, @class, pageDirective); } private static void AddRouteTemplateMetadataAttribute(NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, PageDirective pageDirective) { if (string.IsNullOrEmpty(pageDirective.RouteTemplate)) { return; } var classIndex = @namespace.Children.IndexOf(@class); if (classIndex == -1) { return; } var metadataAttributeNode = new RazorCompiledItemMetadataAttributeIntermediateNode { Key = RouteTemplateKey, Value = pageDirective.RouteTemplate, Source = pageDirective.Source, ValueStringSyntax = "Route" }; // Metadata attributes need to be inserted right before the class declaration. @namespace.Children.Insert(classIndex, metadataAttributeNode); } private void EnsureValidPageDirective(RazorCodeDocument codeDocument, PageDirective pageDirective) { Debug.Assert(pageDirective != null); if (pageDirective.DirectiveNode.IsImported) { pageDirective.DirectiveNode.AddDiagnostic( RazorExtensionsDiagnosticFactory.CreatePageDirective_CannotBeImported(pageDirective.DirectiveNode.Source.AssumeNotNull())); } else { // The document contains a page directive and it is not imported. // We now want to make sure this page directive exists at the top of the file. // We are going to do that by re-parsing the document until the very first line that is not Razor comment // or whitespace. We then make sure the page directive still exists in the re-parsed IR tree. var leadingDirectiveCodeDocument = LeadingDirectiveParsingEngine.CreateCodeDocument(codeDocument.Source); leadingDirectiveCodeDocument = LeadingDirectiveParsingEngine.Engine.Process(leadingDirectiveCodeDocument); var leadingDirectiveDocumentNode = leadingDirectiveCodeDocument.GetRequiredDocumentNode(); if (!PageDirective.TryGetPageDirective(leadingDirectiveDocumentNode, out _)) { // The page directive is not the leading directive. Add an error. pageDirective.DirectiveNode.AddDiagnostic( RazorExtensionsDiagnosticFactory.CreatePageDirective_MustExistAtTheTopOfFile(pageDirective.DirectiveNode.Source.AssumeNotNull())); } } } }