Skip to content

Commit

Permalink
Add Consolidated view classifier to make view types internal (dotnet/…
Browse files Browse the repository at this point in the history
…aspnetcore#30976)

* Add Consolidated view classifier to make view types internal

* Address feedback from peer review

* Address feedback and fix tests

Commit migrated from dotnet/aspnetcore@54d04c5b0a63
  • Loading branch information
captainsafia authored Mar 16, 2021
1 parent 6dd27af commit f26e545
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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 Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;

namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public sealed class ConsolidatedMvcViewDocumentClassifierPass : DocumentClassifierPassBase
{
public static readonly string MvcViewDocumentKind = "mvc.1.0.view";

protected override string DocumentKind => MvcViewDocumentKind;

protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) => true;

protected override void OnDocumentStructureCreated(
RazorCodeDocument codeDocument,
NamespaceDeclarationIntermediateNode @namespace,
ClassDeclarationIntermediateNode @class,
MethodDeclarationIntermediateNode method)
{
base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);

if (!codeDocument.TryComputeNamespace(fallbackToRootNamespace: false, out var namespaceName))
{
@namespace.Content = "AspNetCoreGeneratedDocument";
}
else
{
@namespace.Content = namespaceName;
}

if (!TryComputeClassName(codeDocument, 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 = Checksum.BytesToString(codeDocument.Source.GetChecksum());
@class.ClassName = $"AspNetCore_{checksum}";
}
else
{
@class.ClassName = className;
}

@class.BaseType = "global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>";
@class.Modifiers.Clear();
@class.Modifiers.Add("internal");
@class.Modifiers.Add("sealed");

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}";
}

private bool TryComputeClassName(RazorCodeDocument codeDocument, out string className)
{
var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
if (string.IsNullOrEmpty(filePath))
{
className = null;
return false;
}

className = GetClassNameFromPath(filePath);
return true;
}

private static string GetClassNameFromPath(string path)
{
const string cshtmlExtension = ".cshtml";

if (string.IsNullOrEmpty(path))
{
return path;
}

if (path.EndsWith(cshtmlExtension, StringComparison.OrdinalIgnoreCase))
{
path = path.Substring(0, path.Length - cshtmlExtension.Length);
}

return CSharpIdentifier.SanitizeIdentifier(path);
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
#nullable enable
Microsoft.AspNetCore.Mvc.Razor.Extensions.ConsolidatedMvcViewDocumentClassifierPass
Microsoft.AspNetCore.Mvc.Razor.Extensions.ConsolidatedMvcViewDocumentClassifierPass.ConsolidatedMvcViewDocumentClassifierPass() -> void
~static readonly Microsoft.AspNetCore.Mvc.Razor.Extensions.ConsolidatedMvcViewDocumentClassifierPass.MvcViewDocumentKind -> string
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ public static void Register(RazorProjectEngineBuilder builder)
builder.Features.Add(new PagesPropertyInjectionPass());
builder.Features.Add(new ViewComponentTagHelperPass());
builder.Features.Add(new RazorPageDocumentClassifierPass());
builder.Features.Add(new MvcViewDocumentClassifierPass());

if (builder.Configuration.UseConsolidatedMvcViews)
{
builder.Features.Add(new ConsolidatedMvcViewDocumentClassifierPass());
}
else
{
builder.Features.Add(new MvcViewDocumentClassifierPass());
}

builder.Features.Add(new MvcImportProjectFeature());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// 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.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class ConsolidatedMvcViewDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;

[Fact]
public void ConsolidatedMvcViewDocumentClassifierPass_SetsDifferentNamespace()
{
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", "Test.cshtml"));

var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ConsolidatedMvcViewDocumentClassifierPass
{
Engine = projectEngine.Engine
};

// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);

// Assert
Assert.Equal("AspNetCoreGeneratedDocument", visitor.Namespace.Content);
}

[Fact]
public void ConsolidatedMvcViewDocumentClassifierPass_SetsClass()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: "Test.cshtml");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));

var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ConsolidatedMvcViewDocumentClassifierPass
{
Engine = projectEngine.Engine
};

// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);

// Assert
Assert.Equal("global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>", visitor.Class.BaseType);
Assert.Equal(new[] { "internal", "sealed" }, visitor.Class.Modifiers);
Assert.Equal("Test", visitor.Class.ClassName);
}

[Fact]
public void MvcViewDocumentClassifierPass_NullFilePath_SetsClass()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: null, relativePath: null);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));

var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ConsolidatedMvcViewDocumentClassifierPass
{
Engine = projectEngine.Engine
};

// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);

// Assert
Assert.Equal("global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>", visitor.Class.BaseType);
Assert.Equal(new[] { "internal", "sealed" }, visitor.Class.Modifiers);
Assert.Equal("AspNetCore_d9f877a857a7e9928eac04d09a59f25967624155", visitor.Class.ClassName);
}

[Theory]
[InlineData("/Views/Home/Index.cshtml", "_Views_Home_Index")]
[InlineData("/Areas/MyArea/Views/Home/About.cshtml", "_Areas_MyArea_Views_Home_About")]
public void MvcViewDocumentClassifierPass_UsesRelativePathToGenerateTypeName(string relativePath, string expected)
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: relativePath);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));

var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ConsolidatedMvcViewDocumentClassifierPass
{
Engine = projectEngine.Engine
};

// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);

// Assert
Assert.Equal(expected, visitor.Class.ClassName);
Assert.Equal(new[] { "internal", "sealed" }, visitor.Class.Modifiers);
}

[Fact]
public void ConsolidatedMvcViewDocumentClassifierPass_SetsUpExecuteAsyncMethod()
{
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", "Test.cshtml"));

var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ConsolidatedMvcViewDocumentClassifierPass
{
Engine = projectEngine.Engine
};

// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);

// Assert
Assert.Equal("ExecuteAsync", visitor.Method.MethodName);
Assert.Equal("global::System.Threading.Tasks.Task", visitor.Method.ReturnType);
Assert.Equal(new[] { "public", "async", "override" }, visitor.Method.Modifiers);
}

private static DocumentIntermediateNode CreateIRDocument(RazorProjectEngine projectEngine, RazorCodeDocument codeDocument)
{
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);

if (phase is IRazorIntermediateNodeLoweringPhase)
{
break;
}
}

return codeDocument.GetDocumentIntermediateNode();
}

private class Visitor : IntermediateNodeWalker
{
public NamespaceDeclarationIntermediateNode Namespace { get; private set; }

public ClassDeclarationIntermediateNode Class { get; private set; }

public MethodDeclarationIntermediateNode Method { get; private set; }

public override void VisitMethodDeclaration(MethodDeclarationIntermediateNode node)
{
Method = node;
}

public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
{
Namespace = node;
base.VisitNamespaceDeclaration(node);
}

public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{
Class = node;
base.VisitClassDeclaration(node);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ Microsoft.AspNetCore.Razor.Language.Intermediate.CascadingGenericTypeParameter.C
~Microsoft.AspNetCore.Razor.Language.Intermediate.ComponentIntermediateNode.ProvidesCascadingGenericTypes.set -> void
~Microsoft.AspNetCore.Razor.Language.Intermediate.ComponentTypeInferenceMethodIntermediateNode.ReceivesCascadingGenericTypes.get -> System.Collections.Generic.List<Microsoft.AspNetCore.Razor.Language.Intermediate.CascadingGenericTypeParameter>
~Microsoft.AspNetCore.Razor.Language.Intermediate.ComponentTypeInferenceMethodIntermediateNode.ReceivesCascadingGenericTypes.set -> void
abstract Microsoft.AspNetCore.Razor.Language.RazorConfiguration.UseConsolidatedMvcViews.get -> bool
*REMOVED*~static Microsoft.AspNetCore.Razor.Language.RazorConfiguration.Create(Microsoft.AspNetCore.Razor.Language.RazorLanguageVersion languageVersion, string configurationName, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Razor.Language.RazorExtension> extensions) -> Microsoft.AspNetCore.Razor.Language.RazorConfiguration
~static Microsoft.AspNetCore.Razor.Language.RazorConfiguration.Create(Microsoft.AspNetCore.Razor.Language.RazorLanguageVersion languageVersion, string configurationName, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Razor.Language.RazorExtension> extensions, bool useConsolidatedMvcViews = false) -> Microsoft.AspNetCore.Razor.Language.RazorConfiguration
21 changes: 17 additions & 4 deletions src/Microsoft.AspNetCore.Razor.Language/src/RazorConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ public abstract class RazorConfiguration : IEquatable<RazorConfiguration>
public static readonly RazorConfiguration Default = new DefaultRazorConfiguration(
RazorLanguageVersion.Latest,
"unnamed",
Array.Empty<RazorExtension>());
Array.Empty<RazorExtension>(),
false);

public static RazorConfiguration Create(
RazorLanguageVersion languageVersion,
string configurationName,
IEnumerable<RazorExtension> extensions)
IEnumerable<RazorExtension> extensions,
bool useConsolidatedMvcViews = false)
{
if (languageVersion == null)
{
Expand All @@ -35,7 +37,7 @@ public static RazorConfiguration Create(
throw new ArgumentNullException(nameof(extensions));
}

return new DefaultRazorConfiguration(languageVersion, configurationName, extensions.ToArray());
return new DefaultRazorConfiguration(languageVersion, configurationName, extensions.ToArray(), useConsolidatedMvcViews);
}

public abstract string ConfigurationName { get; }
Expand All @@ -44,6 +46,8 @@ public static RazorConfiguration Create(

public abstract RazorLanguageVersion LanguageVersion { get; }

public abstract bool UseConsolidatedMvcViews { get; }

public override bool Equals(object obj)
{
return base.Equals(obj as RazorConfiguration);
Expand Down Expand Up @@ -71,6 +75,11 @@ public virtual bool Equals(RazorConfiguration other)
return false;
}

if (UseConsolidatedMvcViews != other.UseConsolidatedMvcViews)
{
return false;
}

for (var i = 0; i < Extensions.Count; i++)
{
if (Extensions[i].ExtensionName != other.Extensions[i].ExtensionName)
Expand Down Expand Up @@ -101,18 +110,22 @@ private class DefaultRazorConfiguration : RazorConfiguration
public DefaultRazorConfiguration(
RazorLanguageVersion languageVersion,
string configurationName,
RazorExtension[] extensions)
RazorExtension[] extensions,
bool useConsolidatedMvcViews = false)
{
LanguageVersion = languageVersion;
ConfigurationName = configurationName;
Extensions = extensions;
UseConsolidatedMvcViews = useConsolidatedMvcViews;
}

public override string ConfigurationName { get; }

public override IReadOnlyList<RazorExtension> Extensions { get; }

public override RazorLanguageVersion LanguageVersion { get; }

public override bool UseConsolidatedMvcViews { get; }
}
}
}

0 comments on commit f26e545

Please sign in to comment.