aspnetcore/src/Microsoft.AspNetCore.Mvc.Ra.../RazorPageDocumentClassifier...

136 lines
5.7 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class RazorPageDocumentClassifierPass : DocumentClassifierPassBase
{
public static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page";
public static readonly string RouteTemplateKey = "RouteTemplate";
private static readonly RazorProjectEngine LeadingDirectiveParsingEngine = RazorProjectEngine.Create(
RazorConfiguration.Default,
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.Features.Add(new LeadingDirectiveParserOptionsFeature());
});
protected override string DocumentKind => RazorPageDocumentKind;
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
return PageDirective.TryGetPageDirective(documentNode, out var pageDirective);
}
protected override void OnDocumentStructureCreated(
RazorCodeDocument codeDocument,
NamespaceDeclarationIntermediateNode @namespace,
ClassDeclarationIntermediateNode @class,
MethodDeclarationIntermediateNode method)
{
base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
@namespace.Content = "AspNetCore";
@class.BaseType = "global::Microsoft.AspNetCore.Mvc.RazorPages.Page";
var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
@class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath);
@class.Modifiers.Clear();
@class.Modifiers.Add("public");
method.MethodName = "ExecuteAsync";
method.Modifiers.Clear();
method.Modifiers.Add("public");
method.Modifiers.Add("async");
method.Modifiers.Add("override");
method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}";
var document = codeDocument.GetDocumentIntermediateNode();
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,
};
// 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.Diagnostics.Add(
RazorExtensionsDiagnosticFactory.CreatePageDirective_CannotBeImported(pageDirective.DirectiveNode.Source.Value));
}
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 = RazorCodeDocument.Create(codeDocument.Source);
LeadingDirectiveParsingEngine.Engine.Process(leadingDirectiveCodeDocument);
var documentIRNode = leadingDirectiveCodeDocument.GetDocumentIntermediateNode();
if (!PageDirective.TryGetPageDirective(documentIRNode, out var _))
{
// The page directive is not the leading directive. Add an error.
pageDirective.DirectiveNode.Diagnostics.Add(
RazorExtensionsDiagnosticFactory.CreatePageDirective_MustExistAtTheTopOfFile(pageDirective.DirectiveNode.Source.Value));
}
}
}
private class LeadingDirectiveParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorParserOptionsFeature
{
public int Order { get; }
public void Configure(RazorParserOptionsBuilder options)
{
options.ParseLeadingDirectives = true;
}
}
}
}