Add support for 2-phase compile

This commit is contained in:
Pranav K 2018-11-07 16:43:09 -08:00
parent f9623c2c27
commit 8d629371bf
83 changed files with 1986 additions and 419 deletions

View File

@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
InjectDirective.Register(builder);
ModelDirective.Register(builder);
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
builder.Features.Add(new DefaultTagHelperDescriptorProvider());

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
NamespaceDirective.Register(builder);
PageDirective.Register(builder);
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);

View File

@ -2,6 +2,7 @@
// 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.Components;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{

View File

@ -44,6 +44,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind &&
documentNode.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind)
{
// Not a MVC file. Skip.
return;
}
var visitor = new Visitor();
visitor.Visit(documentNode);
var modelType = ModelDirective.GetModelType(documentNode);

View File

@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Microsoft.AspNetCore.Razor.Language\CodeGeneration\CodeWriterExtensions.cs">
<Link>Shared\CodeWriterExtensions.cs</Link>
</Compile>
<Compile Include="..\Microsoft.AspNetCore.Razor.Language\CodeGeneration\CodeWriterExtensions.cs" Link="Shared\CodeWriterExtensions.cs" />
<Compile Include="..\Microsoft.AspNetCore.Razor.Language\CSharpIdentifier.cs" Link="Shared\CSharpIdentifier.cs" />
<Compile Include="..\Microsoft.AspNetCore.Razor.Language\Checksum.cs" Link="Shared\Checksum.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,6 @@
// 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.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -31,12 +30,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
// 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 = BytesToString(codeDocument.Source.GetChecksum());
var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
@class.ClassName = $"AspNetCore_{checksum}";
}
else
{
@class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath);
@class.ClassName = GetClassNameFromPath(filePath);
}
@class.BaseType = "global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>";
@ -51,21 +50,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}";
}
private static string BytesToString(byte[] bytes)
private static string GetClassNameFromPath(string path)
{
if (bytes == null)
const string cshtmlExtension = ".cshtml";
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(bytes));
return path;
}
var result = new StringBuilder(bytes.Length);
for (var i = 0; i < bytes.Length; i++)
if (path.EndsWith(cshtmlExtension, StringComparison.OrdinalIgnoreCase))
{
// The x2 format means lowercase hex, where each byte is a 2-character string.
result.Append(bytes[i].ToString("x2"));
path = path.Substring(0, path.Length - cshtmlExtension.Length);
}
return result.ToString();
return CSharpIdentifier.SanitizeIdentifier(path);
}
}
}

View File

@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
for (var i = 0; i < segments.Length - 1; i++)
{
builder.Append('.');
builder.Append(CSharpIdentifier.SanitizeClassName(segments[i]));
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
}
return builder.ToString();

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
NamespaceDirective.Register(builder);
PageDirective.Register(builder);
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);

View File

@ -3,7 +3,6 @@
using System;
using System.Diagnostics;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -58,12 +57,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
// 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 = BytesToString(codeDocument.Source.GetChecksum());
var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
@class.ClassName = $"AspNetCore_{checksum}";
}
else
{
@class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath);
@class.ClassName = GetClassNameFromPath(filePath);
}
@class.Modifiers.Clear();
@ -144,21 +143,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
}
}
private static string BytesToString(byte[] bytes)
private static string GetClassNameFromPath(string path)
{
if (bytes == null)
const string cshtmlExtension = ".cshtml";
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(bytes));
return path;
}
var result = new StringBuilder(bytes.Length);
for (var i = 0; i < bytes.Length; i++)
if (path.EndsWith(cshtmlExtension, StringComparison.OrdinalIgnoreCase))
{
// The x2 format means lowercase hex, where each byte is a 2-character string.
result.Append(bytes[i].ToString("x2"));
path = path.Substring(0, path.Length - cshtmlExtension.Length);
}
return result.ToString();
return CSharpIdentifier.SanitizeIdentifier(path);
}
}
}

View File

@ -1,31 +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 System;
using System.Globalization;
using System.Text;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
namespace Microsoft.AspNetCore.Razor.Language
{
internal static class CSharpIdentifier
{
private const string CshtmlExtension = ".cshtml";
public static string GetClassNameFromPath(string path)
{
if (string.IsNullOrEmpty(path))
{
return path;
}
if (path.EndsWith(CshtmlExtension, StringComparison.OrdinalIgnoreCase))
{
path = path.Substring(0, path.Length - CshtmlExtension.Length);
}
return SanitizeClassName(path);
}
// CSharp Spec §2.4.2
private static bool IsIdentifierStart(char character)
{
@ -51,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
category == UnicodeCategory.Format; // Cf
}
public static string SanitizeClassName(string inputName)
public static string SanitizeIdentifier(string inputName)
{
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
{

View File

@ -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.
namespace Microsoft.AspNetCore.Razor.Language.Components
{
// Constants for method names used in code-generation
// Keep these in sync with the actual definitions
internal static class CodeGenerationConstants
{
public static class RazorComponent
{
public const string FullTypeName = "Microsoft.AspNetCore.Components.Component";
public const string BuildRenderTree = "BuildRenderTree";
public const string BuildRenderTreeParameter = "builder";
}
public static class RenderTreeBuilder
{
public const string FullTypeName = "Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder";
}
public static class InjectDirective
{
public const string FullTypeName = "Microsoft.AspNetCore.Razor.Components.InjectAttribute";
}
}
}

View File

@ -0,0 +1,139 @@
// 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.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ComponentDocumentClassifierPass : DocumentClassifierPassBase
{
public static readonly string ComponentDocumentKind = "component.1.0";
private static readonly object BuildRenderTreeBaseCallAnnotation = new object();
private static readonly char[] PathSeparators = new char[] { '/', '\\' };
private static readonly char[] NamespaceSeparators = new char[] { '.' };
protected override string DocumentKind => ComponentDocumentKind;
// Ensure this runs before the MVC classifiers which have Order = 0
public override int Order => -100;
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
return codeDocument.GetInputDocumentKind() == InputDocumentKind.Component;
}
protected override void OnDocumentStructureCreated(RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method)
{
base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
if (!TryComputeNamespaceAndClass(
codeDocument.Source.FilePath,
codeDocument.Source.RelativePath,
out var computedNamespace,
out var computedClass))
{
// If we can't compute a nice namespace (no relative path) then just generate something
// mangled.
computedNamespace = "AspNetCore";
var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
computedClass = $"AspNetCore_{checksum}";
}
@namespace.Content = computedNamespace;
@class.ClassName = computedClass;
@class.BaseType = $"global::{CodeGenerationConstants.RazorComponent.FullTypeName}";
var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
if (string.IsNullOrEmpty(filePath))
{
// 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 = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(filePath));
}
@class.Modifiers.Clear();
@class.Modifiers.Add("public");
@class.Modifiers.Add("sealed");
method.MethodName = CodeGenerationConstants.RazorComponent.BuildRenderTree;
method.ReturnType = "void";
method.Modifiers.Clear();
method.Modifiers.Add("public");
method.Modifiers.Add("override");
method.Parameters.Clear();
method.Parameters.Add(new MethodParameter()
{
TypeName = CodeGenerationConstants.RenderTreeBuilder.FullTypeName,
ParameterName = CodeGenerationConstants.RazorComponent.BuildRenderTreeParameter,
});
// We need to call the 'base' method as the first statement.
var callBase = new CSharpCodeIntermediateNode();
callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true);
callBase.Children.Add(new IntermediateToken
{
Kind = TokenKind.CSharp,
Content = $"base.{CodeGenerationConstants.RazorComponent.BuildRenderTree}({CodeGenerationConstants.RazorComponent.BuildRenderTreeParameter});"
});
method.Children.Insert(0, callBase);
}
private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class)
{
if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length)
{
@namespace = null;
@class = null;
return false;
}
// Try and infer a namespace from the project directory. We don't yet have the ability to pass
// the namespace through from the project.
var trimLength = relativePath.Length + (relativePath.StartsWith("/") ? 0 : 1);
var baseDirectory = filePath.Substring(0, filePath.Length - trimLength);
var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators);
var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1);
if (string.IsNullOrEmpty(baseNamespace))
{
@namespace = null;
@class = null;
return false;
}
var builder = new StringBuilder();
// Sanitize the base namespace, but leave the dots.
var segments = baseNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0]));
for (var i = 1; i < segments.Length; i++)
{
builder.Append('.');
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
}
segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries);
// Skip the last segment because it's the FileName.
for (var i = 0; i < segments.Length - 1; i++)
{
builder.Append('.');
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
}
@namespace = builder.ToString();
@class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath));
return true;
}
}
}

View File

@ -0,0 +1,22 @@
// 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.Extensions;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
public static class ComponentExtensions
{
public static void Register(RazorProjectEngineBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
FunctionsDirective.Register(builder);
builder.Features.Add(new ComponentDocumentClassifierPass());
}
}
}

View File

@ -10,13 +10,15 @@ namespace Microsoft.AspNetCore.Razor.Language
int indentSize,
bool designTime,
bool suppressChecksum,
bool supressMetadataAttributes)
bool supressMetadataAttributes,
bool suppressPrimaryMethodBody)
{
IndentWithTabs = indentWithTabs;
IndentSize = indentSize;
DesignTime = designTime;
SuppressChecksum = suppressChecksum;
SuppressMetadataAttributes = supressMetadataAttributes;
SuppressPrimaryMethodBody = suppressPrimaryMethodBody;
}
public override bool DesignTime { get; }

View File

@ -31,10 +31,16 @@ namespace Microsoft.AspNetCore.Razor.Language
public override bool IndentWithTabs { get; set; }
public override bool SuppressChecksum { get; set; }
public override RazorCodeGenerationOptions Build()
{
return new DefaultRazorCodeGenerationOptions(IndentWithTabs, IndentSize, DesignTime, SuppressChecksum, SuppressMetadataAttributes);
return new DefaultRazorCodeGenerationOptions(
IndentWithTabs,
IndentSize,
DesignTime,
SuppressChecksum,
SuppressMetadataAttributes,
SuppressPrimaryMethodBody);
}
public override void SetDesignTime(bool designTime)

View File

@ -0,0 +1,71 @@
// 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.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
internal sealed class EliminateMethodBodyPass : IntermediateNodePassBase, IRazorOptimizationPass
{
// Run early in the optimization phase
public override int Order => int.MinValue;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (codeDocument == null)
{
throw new ArgumentNullException(nameof(codeDocument));
}
if (documentNode == null)
{
throw new ArgumentNullException(nameof(documentNode));
}
var codeGenerationOptions = codeDocument.GetCodeGenerationOptions();
if (codeGenerationOptions == null || !codeGenerationOptions.SuppressPrimaryMethodBody)
{
return;
}
var method = documentNode.FindPrimaryMethod();
if (method == null)
{
return;
}
method.Children.Clear();
// After we clear all of the method body there might be some unused fields, which can be
// blocking if compiling with warnings as errors. Suppress this warning so that it doesn't
// get annoying in VS.
documentNode.Children.Insert(documentNode.Children.IndexOf(documentNode.FindPrimaryNamespace()), new CSharpCodeIntermediateNode()
{
Children =
{
// Field is assigned but never used
new IntermediateToken()
{
Content = "#pragma warning disable 0414" + Environment.NewLine,
Kind = TokenKind.CSharp,
},
// Field is never assigned
new IntermediateToken()
{
Content = "#pragma warning disable 0649" + Environment.NewLine,
Kind = TokenKind.CSharp,
},
// Field is never used
new IntermediateToken()
{
Content = "#pragma warning disable 0169" + Environment.NewLine,
Kind = TokenKind.CSharp,
},
},
});
}
}
}

View File

@ -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.
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
internal static class InputDocumentKind
{
public static readonly string Component = "component";
public static readonly string MvcFile = "mvc";
}
}

View File

@ -169,6 +169,26 @@ namespace Microsoft.AspNetCore.Razor.Language
document.Items[typeof(RazorCodeGenerationOptions)] = codeGenerationOptions;
}
public static string GetInputDocumentKind(this RazorCodeDocument document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
return (string)document.Items[typeof(InputDocumentKind)];
}
public static void SetInputDocumentKind(this RazorCodeDocument document, string kind)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
document.Items[typeof(InputDocumentKind)] = kind;
}
private class ImportSyntaxTreesHolder
{
public ImportSyntaxTreesHolder(IReadOnlyList<RazorSyntaxTree> syntaxTrees)

View File

@ -14,7 +14,8 @@ namespace Microsoft.AspNetCore.Razor.Language
indentSize: 4,
designTime: false,
suppressChecksum: false,
supressMetadataAttributes: false);
supressMetadataAttributes: false,
suppressPrimaryMethodBody: false);
}
public static RazorCodeGenerationOptions CreateDesignTimeDefault()
@ -24,7 +25,8 @@ namespace Microsoft.AspNetCore.Razor.Language
indentSize: 4,
designTime: true,
suppressChecksum: false,
supressMetadataAttributes: true);
supressMetadataAttributes: true,
suppressPrimaryMethodBody: false);
}
public static RazorCodeGenerationOptions Create(Action<RazorCodeGenerationOptionsBuilder> configure)
@ -93,5 +95,10 @@ namespace Microsoft.AspNetCore.Razor.Language
/// </para>
/// </remarks>
public virtual bool SuppressMetadataAttributes { get; protected set; }
/// <summary>
/// Gets or sets a value that determines if an empty body is generated for the primary method.
/// </summary>
public virtual bool SuppressPrimaryMethodBody { get; protected set; }
}
}

View File

@ -42,6 +42,11 @@ namespace Microsoft.AspNetCore.Razor.Language
/// </remarks>
public virtual bool SuppressMetadataAttributes { get; set; }
/// <summary>
/// Gets or sets a value that determines if an empty body is generated for the primary method.
/// </summary>
public virtual bool SuppressPrimaryMethodBody { get; set; }
public abstract RazorCodeGenerationOptions Build();
public virtual void SetDesignTime(bool designTime)

View File

@ -3,8 +3,8 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace Microsoft.AspNetCore.Razor.Language
@ -176,6 +176,7 @@ namespace Microsoft.AspNetCore.Razor.Language
features.Add(new DirectiveRemovalOptimizationPass());
features.Add(new DefaultTagHelperOptimizationPass());
features.Add(new PreallocatedTagHelperAttributeOptimizationPass());
features.Add(new EliminateMethodBodyPass());
// Default Code Target Extensions
var targetExtensionFeature = new DefaultRazorTargetExtensionFeature();
@ -217,10 +218,15 @@ namespace Microsoft.AspNetCore.Razor.Language
// For now we only handle AssemblyExtension - which is not user-constructable. We're keeping a tight
// lid on how things work until we add official support for extensibility everywhere. So, this is
// intentionally inflexible for the time being.
var extension = extensions[i] as AssemblyExtension;
var initializer = extension?.CreateInitializer();
initializer?.Initialize(builder);
if (extensions[i] is AssemblyExtension extension)
{
var initializer = extension.CreateInitializer();
initializer?.Initialize(builder);
}
}
// Default extensions.
ComponentExtensions.Register(builder);
}
}
}

View File

@ -6,5 +6,7 @@ namespace Microsoft.AspNetCore.Razor.Language
public static class TagHelperConventions
{
public static readonly string DefaultKind = "ITagHelper";
public static readonly string ComponentKind = "IComponent";
}
}

View File

@ -141,6 +141,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
b.Features.Add(new DefaultMetadataReferenceFeature() { References = metadataReferences });
b.Features.Add(new CompilationTagHelperFeature());
b.Features.Add(new DefaultTagHelperDescriptorProvider());
b.Features.Add(new ComponentTagHelperDescriptorProvider());
});
var feature = engine.Engine.Features.OfType<ITagHelperFeature>().Single();

View File

@ -5,8 +5,10 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json;
@ -21,12 +23,14 @@ namespace Microsoft.AspNetCore.Razor.Tools
Sources = Option("-s", ".cshtml files to compile", CommandOptionType.MultipleValue);
Outputs = Option("-o", "Generated output file path", CommandOptionType.MultipleValue);
RelativePaths = Option("-r", "Relative path", CommandOptionType.MultipleValue);
DocumentKinds = Option("-k", "Document kind", CommandOptionType.MultipleValue);
ProjectDirectory = Option("-p", "project root directory", CommandOptionType.SingleValue);
TagHelperManifest = Option("-t", "tag helper manifest file", CommandOptionType.SingleValue);
Version = Option("-v|--version", "Razor language version", CommandOptionType.SingleValue);
Configuration = Option("-c", "Razor configuration name", CommandOptionType.SingleValue);
ExtensionNames = Option("-n", "extension name", CommandOptionType.MultipleValue);
ExtensionFilePaths = Option("-e", "extension file path", CommandOptionType.MultipleValue);
GenerateDeclaration = Option("--generate-declaration", "Generate declaration", CommandOptionType.NoValue);
}
public CommandOption Sources { get; }
@ -35,6 +39,8 @@ namespace Microsoft.AspNetCore.Razor.Tools
public CommandOption RelativePaths { get; }
public CommandOption DocumentKinds { get; }
public CommandOption ProjectDirectory { get; }
public CommandOption TagHelperManifest { get; }
@ -47,6 +53,8 @@ namespace Microsoft.AspNetCore.Razor.Tools
public CommandOption ExtensionFilePaths { get; }
public CommandOption GenerateDeclaration { get; }
protected override Task<int> ExecuteCoreAsync()
{
if (!Parent.Checker.Check(ExtensionFilePaths.Values))
@ -66,13 +74,13 @@ namespace Microsoft.AspNetCore.Razor.Tools
var version = RazorLanguageVersion.Parse(Version.Value());
var configuration = RazorConfiguration.Create(version, Configuration.Value(), extensions);
var sourceItems = GetSourceItems(ProjectDirectory.Value(), Sources.Values, Outputs.Values, RelativePaths.Values, DocumentKinds.Values);
var result = ExecuteCore(
configuration: configuration,
projectDirectory: ProjectDirectory.Value(),
tagHelperManifest: TagHelperManifest.Value(),
sources: Sources.Values,
outputs: Outputs.Values,
relativePaths: RelativePaths.Values);
sourceItems: sourceItems);
return Task.FromResult(result);
}
@ -88,11 +96,21 @@ namespace Microsoft.AspNetCore.Razor.Tools
if (Outputs.Values.Count != Sources.Values.Count)
{
Error.WriteLine($"{Sources.Description} has {Sources.Values.Count}, but {Outputs.Description} has {Outputs.Values.Count} values.");
return false;
}
if (RelativePaths.Values.Count != Sources.Values.Count)
{
Error.WriteLine($"{Sources.Description} has {Sources.Values.Count}, but {RelativePaths.Description} has {RelativePaths.Values.Count} values.");
return false;
}
if (DocumentKinds.Values.Count != 0 && DocumentKinds.Values.Count != Sources.Values.Count)
{
// 2.x tasks do not specify DocumentKinds - in which case, no values will be present. If a kind for one document is specified, we expect as many kind entries
// as sources.
Error.WriteLine($"{Sources.Description} has {Sources.Values.Count}, but {DocumentKinds.Description} has {DocumentKinds.Values.Count} values.");
return false;
}
if (string.IsNullOrEmpty(ProjectDirectory.Value()))
@ -138,26 +156,30 @@ namespace Microsoft.AspNetCore.Razor.Tools
RazorConfiguration configuration,
string projectDirectory,
string tagHelperManifest,
List<string> sources,
List<string> outputs,
List<string> relativePaths)
SourceItem[] sourceItems)
{
tagHelperManifest = Path.Combine(projectDirectory, tagHelperManifest);
var tagHelpers = GetTagHelpers(tagHelperManifest);
var inputItems = GetInputItems(projectDirectory, sources, outputs, relativePaths);
var compositeFileSystem = new CompositeRazorProjectFileSystem(new[]
{
GetVirtualRazorProjectSystem(inputItems),
GetVirtualRazorProjectSystem(sourceItems),
RazorProjectFileSystem.Create(projectDirectory),
});
var engine = RazorProjectEngine.Create(configuration, compositeFileSystem, b =>
{
b.Features.Add(new StaticTagHelperFeature() { TagHelpers = tagHelpers, });
b.Features.Add(new InputDocumentKindClassifierPass(sourceItems));
if (GenerateDeclaration.HasValue())
{
b.Features.Add(new SetSuppressPrimaryMethodBodyOptionFeature());
}
});
var results = GenerateCode(engine, inputItems);
var results = GenerateCode(engine, sourceItems);
var success = true;
@ -229,13 +251,15 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
}
private SourceItem[] GetInputItems(string projectDirectory, List<string> sources, List<string> outputs, List<string> relativePath)
private SourceItem[] GetSourceItems(string projectDirectory, List<string> sources, List<string> outputs, List<string> relativePath, List<string> documentKinds)
{
var items = new SourceItem[sources.Count];
for (var i = 0; i < items.Length; i++)
{
var outputPath = Path.Combine(projectDirectory, outputs[i]);
items[i] = new SourceItem(sources[i], outputs[i], relativePath[i]);
var documentKind = documentKinds.Count > 0 ? documentKinds[i] : "mvc";
items[i] = new SourceItem(sources[i], outputs[i], relativePath[i], documentKind);
}
return items;
@ -271,9 +295,9 @@ namespace Microsoft.AspNetCore.Razor.Tools
public RazorCSharpDocument CSharpDocument { get; }
}
private struct SourceItem
private readonly struct SourceItem
{
public SourceItem(string sourcePath, string outputPath, string physicalRelativePath)
public SourceItem(string sourcePath, string outputPath, string physicalRelativePath, string documentKind)
{
SourcePath = sourcePath;
OutputPath = outputPath;
@ -281,6 +305,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
FilePath = '/' + physicalRelativePath
.Replace(Path.DirectorySeparatorChar, '/')
.Replace("//", "/");
DocumentKind = documentKind;
}
public string SourcePath { get; }
@ -290,6 +315,8 @@ namespace Microsoft.AspNetCore.Razor.Tools
public string RelativePhysicalPath { get; }
public string FilePath { get; }
public string DocumentKind { get; }
}
private class StaticTagHelperFeature : ITagHelperFeature
@ -300,5 +327,46 @@ namespace Microsoft.AspNetCore.Razor.Tools
public IReadOnlyList<TagHelperDescriptor> GetDescriptors() => TagHelpers;
}
private class SetSuppressPrimaryMethodBodyOptionFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
options.SuppressPrimaryMethodBody = true;
}
}
private class InputDocumentKindClassifierPass : RazorEngineFeatureBase, IRazorDocumentClassifierPass
{
public InputDocumentKindClassifierPass(SourceItem[] sourceItems)
{
DocumentKinds = new Dictionary<string, string>(sourceItems.Length, StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < sourceItems.Length; i++)
{
var item = sourceItems[i];
DocumentKinds[item.SourcePath] = item.DocumentKind;
}
}
// Run before other document classifiers
public int Order => -1000;
public Dictionary<string, string> DocumentKinds { get; }
public void Execute(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (DocumentKinds.TryGetValue(codeDocument.Source.FilePath, out var kind))
{
codeDocument.SetInputDocumentKind(kind);
}
}
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Razor is a markup syntax for adding server-side logic to web pages. This assembly contains infrastructure supporting Razor MSBuild integration.</Description>

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
/// Strings are encoded via a length prefix as a signed
/// 32-bit integer, followed by an array of characters.
/// </summary>
internal struct RequestArgument
internal readonly struct RequestArgument
{
public readonly ArgumentId Id;
public readonly int ArgumentIndex;
@ -63,5 +63,10 @@ namespace Microsoft.AspNetCore.Razor.Tools
// The directory to use for temporary operations.
TempDirectory,
}
public override string ToString()
{
return $"{Id} {Value}";
}
}
}

View File

@ -0,0 +1,132 @@
// 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.Language;
namespace Microsoft.CodeAnalysis.Razor
{
public class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
{
public int Order { get; set; }
public void Execute(TagHelperDescriptorProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var compilation = context.GetCompilation();
if (compilation == null)
{
// No compilation, nothing to do.
return;
}
var componentSymbol = compilation.GetTypeByMetadataName(TagHelperTypes.IComponent);
if (componentSymbol == null || componentSymbol.TypeKind == TypeKind.Error)
{
// Could not find attributes we care about in the compilation. Nothing to do.
return;
}
var types = new List<INamedTypeSymbol>();
var visitor = new ComponentTypeVisitor(componentSymbol, types);
// We always visit the global namespace.
visitor.Visit(compilation.Assembly.GlobalNamespace);
foreach (var reference in compilation.References)
{
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
{
if (IsTagHelperAssembly(assembly))
{
visitor.Visit(assembly.GlobalNamespace);
}
}
}
for (var i = 0; i < types.Count; i++)
{
var type = types[i];
var descriptor = CreateDescriptor(type);
if (descriptor != null)
{
context.Results.Add(descriptor);
}
}
}
private static TagHelperDescriptor CreateDescriptor(INamedTypeSymbol type)
{
var typeName = type.ToDisplayString(DefaultTagHelperDescriptorFactory.FullNameTypeDisplayFormat);
var assemblyName = type.ContainingAssembly.Identity.Name;
var descriptorBuilder = TagHelperDescriptorBuilder.Create(TagHelperConventions.ComponentKind, typeName, assemblyName);
descriptorBuilder.SetTypeName(typeName);
descriptorBuilder.Metadata[TagHelperMetadata.Runtime.Name] = TagHelperConventions.ComponentKind;
// Components have very simple matching rules. The type name (short) matches the tag name.
descriptorBuilder.TagMatchingRule(r => r.TagName = type.Name);
var descriptor = descriptorBuilder.Build();
return descriptor;
}
private bool IsTagHelperAssembly(IAssemblySymbol assembly)
{
return assembly.Name != null && !assembly.Name.StartsWith("System.", StringComparison.Ordinal);
}
// Visits top-level types and finds interface implementations.
internal class ComponentTypeVisitor : SymbolVisitor
{
private readonly INamedTypeSymbol _componentSymbol;
private readonly List<INamedTypeSymbol> _results;
public ComponentTypeVisitor(INamedTypeSymbol componentSymbol, List<INamedTypeSymbol> results)
{
_componentSymbol = componentSymbol;
_results = results;
}
public override void VisitNamedType(INamedTypeSymbol symbol)
{
if (IsComponent(symbol))
{
_results.Add(symbol);
}
}
public override void VisitNamespace(INamespaceSymbol symbol)
{
foreach (var member in symbol.GetMembers())
{
Visit(member);
}
}
public override void VisitAssembly(IAssemblySymbol symbol)
{
// This as a simple yet high-value optimization that excludes the vast majority of
// assemblies that (by definition) can't contain a component.
if (symbol.Name != null && !symbol.Name.StartsWith("System.", StringComparison.Ordinal))
{
Visit(symbol.GlobalNamespace);
}
}
internal bool IsComponent(INamedTypeSymbol symbol)
{
return
symbol.DeclaredAccessibility == Accessibility.Public &&
!symbol.IsAbstract &&
symbol.AllInterfaces.Contains(_componentSymbol);
}
}
}
}

View File

@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.Razor
private readonly INamedTypeSymbol _restrictChildrenAttributeSymbol;
private readonly INamedTypeSymbol _editorBrowsableAttributeSymbol;
private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
internal static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
SymbolDisplayFormat.FullyQualifiedFormat
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
.WithMiscellaneousOptions(SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions & (~SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
@ -434,6 +434,6 @@ namespace Microsoft.CodeAnalysis.Razor
return false;
}
private static string GetFullName(ITypeSymbol type) => type.ToDisplayString(FullNameTypeDisplayFormat);
protected static string GetFullName(ITypeSymbol type) => type.ToDisplayString(FullNameTypeDisplayFormat);
}
}

View File

@ -7,6 +7,8 @@ namespace Microsoft.CodeAnalysis.Razor
{
public const string ITagHelper = "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper";
public const string IComponent = "Microsoft.AspNetCore.Components.IComponent";
public const string IDictionary = "System.Collections.Generic.IDictionary`2";
public const string HtmlAttributeNameAttribute = "Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute";

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
};
private const string GeneratedOutput = "GeneratedOutput";
private const string DocumentKind = "DocumentKind";
private const string TargetPath = "TargetPath";
private const string FullPath = "FullPath";
private const string Identity = "Identity";
@ -41,6 +42,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
[Required]
public string TagHelperManifest { get; set; }
public bool GenerateDeclaration { get; set; }
internal override string Command => "generate";
protected override bool ValidateParameters()
@ -99,6 +102,13 @@ namespace Microsoft.AspNetCore.Razor.Tasks
builder.AppendLine("-o");
var outputPath = Path.Combine(ProjectRoot, input.GetMetadata(GeneratedOutput));
builder.AppendLine(outputPath);
var kind = input.GetMetadata(DocumentKind);
if (!string.IsNullOrEmpty(kind))
{
builder.AppendLine("-k");
builder.AppendLine(kind);
}
}
builder.AppendLine("-p");
@ -113,6 +123,11 @@ namespace Microsoft.AspNetCore.Razor.Tasks
builder.AppendLine("-c");
builder.AppendLine(Configuration[0].GetMetadata(Identity));
if (GenerateDeclaration)
{
builder.AppendLine("--generate-declaration");
}
for (var i = 0; i < Extensions.Length; i++)
{
builder.AppendLine("-n");

View File

@ -70,10 +70,15 @@ Copyright (c) .NET Foundation. All rights reserved.
</ItemGroup>
</Target>
<Target
Name="_RazorEnsureCompiled"
Condition="!Exists('$(_RazorGenerateTargetReferenceAssembly)')"
DependsOnTargets="Compile" />
<Target
Name="ResolveTagHelperRazorGenerateInputs"
DependsOnTargets="_EnsureRazorTasksAssemblyDefined;Compile"
Inputs="$(MSBuildAllProjects);@(RazorReferencePath)"
Inputs="$(MSBuildAllProjects);@(RazorReferencePath);$(_RazorGenerateTargetReferenceAssembly)"
DependsOnTargets="_RazorEnsureCompiled"
Outputs="$(_RazorTagHelperInputCache)"
Condition="'@(RazorGenerateWithTargetPath)' != ''">
@ -100,7 +105,7 @@ Copyright (c) .NET Foundation. All rights reserved.
Version="$(RazorLangVersion)"
Configuration="@(ResolvedRazorConfiguration)"
Extensions="@(ResolvedRazorExtension)"
Assemblies="@(RazorReferencePath)"
Assemblies="@(RazorReferencePath);$(_RazorGenerateTargetReferenceAssembly)"
ProjectRoot="$(MSBuildProjectDirectory)"
TagHelperManifest="$(_RazorTagHelperOutputCache)">
<Output
@ -121,7 +126,6 @@ Copyright (c) .NET Foundation. All rights reserved.
<PropertyGroup>
<RazorCoreGenerateDependsOn>
_EnsureRazorTasksAssemblyDefined;
_HashRazorGenerateInputs;
_ResolveRazorGenerateOutputs;
</RazorCoreGenerateDependsOn>
@ -167,14 +171,10 @@ Copyright (c) .NET Foundation. All rights reserved.
<Target Name="_ResolveGeneratedRazorCompileInputs">
<ItemGroup>
<RazorCompile Include="@(_RazorGenerateOutput)" />
<RazorCompile
Include="%(RazorGenerateWithTargetPath.GeneratedOutput)"
Condition="'%(RazorGenerateWithTargetPath.DocumentKind)'=='mvc'" />
</ItemGroup>
</Target>
<Target Name="_EnsureRazorTasksAssemblyDefined">
<Error
Text="Assembly location for Razor SDK Tasks was not specified. The most likely cause is an older incompatible version of Microsoft.NET.Sdk.Razor, or Microsoft.NET.Sdk.Web used by this project. Please target a newer version of the .NET Core SDK."
Condition="'$(RazorSdkBuildTasksAssembly)' == ''" />
</Target>
</Project>

View File

@ -54,6 +54,7 @@ Copyright (c) .NET Foundation. All rights reserved.
Inputs="$(MSBuildAllProjects);
@(RazorCompile);
$(AssemblyOriginatorKeyFile);
$(_RazorCompileTargetReferenceAssembly);
@(RazorReferencePath);
@(CompiledLicenseFile);
@(LinkResource);
@ -147,7 +148,7 @@ Copyright (c) .NET Foundation. All rights reserved.
Prefer32Bit="$(Prefer32Bit)"
PreferredUILang="$(PreferredUILang)"
ProvideCommandLineArgs="$(ProvideCommandLineArgs)"
References="@(RazorReferencePath)"
References="@(RazorReferencePath);$(_RazorCompileTargetReferenceAssembly)"
ReportAnalyzer="$(ReportAnalyzer)"
Resources="@(_RazorCoreCompileResourceInputs);@(CompiledLicenseFile)"
ResponseFiles="$(CompilerResponseFile)"

View File

@ -0,0 +1,285 @@
<!--
***********************************************************************************************
Microsoft.NET.Sdk.Razor.Component.targets
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0">
<PropertyGroup>
<!-- Used for tracking inputs to component generation -->
<_RazorComponentInputHash></_RazorComponentInputHash>
<_RazorComponentInputCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).RazorComponent.input.cache</_RazorComponentInputCacheFile>
</PropertyGroup>
<ItemGroup>
<!-- Path used for the temporary compilation we produce for component discovery -->
<_RazorComponentDeclarationAssembly Include="$(_RazorComponentDeclarationOutputPath)$(TargetName).dll" />
</ItemGroup>
<Target Name="_HashRazorComponentInputs" Condition="'@(RazorComponentWithTargetPath->Count())'!='0'">
<Hash ItemsToHash="@(RazorComponentWithTargetPath)">
<Output TaskParameter="HashResult" PropertyName="_RazorComponentInputHash" />
</Hash>
<MakeDir
Directories="$(IntermediateOutputPath)"
Condition="!Exists('$(IntermediateOutputPath)')" />
<WriteLinesToFile
Lines="$(_RazorComponentInputHash)"
File="$(_RazorComponentInputCacheFile)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(_RazorComponentInputCacheFile)" />
</ItemGroup>
</Target>
<Target Name="_ResolveRazorComponentOutputs" Condition="'@(RazorComponentWithTargetPath->Count())'!='0'">
<Error
Text="RazorComponentWithTargetPath item '%(RazorComponentWithTargetPath.Identity)' does not specify required metadata 'GeneratedDeclaration'."
Condition="'%(RazorComponentWithTargetPath.GeneratedDeclaration)' == ''" />
<ItemGroup>
<_RazorComponentDeclaration Include="%(RazorComponentWithTargetPath.GeneratedDeclaration)" />
<_RazorComponentDefinition Include="%(RazorComponentWithTargetPath.GeneratedOutput)" />
</ItemGroup>
</Target>
<PropertyGroup>
<RazorGenerateComponentDeclarationDependsOn>
_HashRazorComponentInputs;
_ResolveRazorComponentOutputs;
</RazorGenerateComponentDeclarationDependsOn>
</PropertyGroup>
<!--
Generates 'declaration' files for each component, that only have that class and member declarations.
These files participate in the design-time-build for intellisense, and are used at build-time
when discovering components for a 'real' build.
-->
<Target
Name="RazorGenerateComponentDeclaration"
DependsOnTargets="$(RazorGenerateComponentDeclarationDependsOn)"
Inputs="$(MSBuildAllProjects);@(RazorComponentWithTargetPath);$(_RazorComponentInputCacheFile)"
Outputs="@(_RazorComponentDeclaration)"
Condition="'@(RazorComponentWithTargetPath->Count())'!='0'">
<ItemGroup>
<_RazorComponentDeclarationSources Include="@(RazorComponentWithTargetPath)">
<GeneratedOutput>%(RazorComponentWithTargetPath.GeneratedDeclaration)</GeneratedOutput>
</_RazorComponentDeclarationSources>
</ItemGroup>
<MakeDir
Directories="%(_RazorComponentDeclaration.RelativeDir)"
Condition="!Exists('%(_RazorComponentDeclaration.RelativeDir)')" />
<PropertyGroup>
<!-- This file will not exist, but we need some value -->
<_RazorComponentDeclarationManifest>$(IntermediateOutputPath)$(MSBuildProjectName).RazorComponents.declaration.json</_RazorComponentDeclarationManifest>
</PropertyGroup>
<RazorGenerate
Debug="$(_RazorDebugGenerateCodeTask)"
DebugTool="$(_RazorDebugGenerateCodeTool)"
ToolAssembly="$(_RazorToolAssembly)"
UseServer="$(UseRazorBuildServer)"
ForceServer="$(_RazorForceBuildServer)"
PipeName="$(_RazorBuildServerPipeName)"
Version="$(RazorLangVersion)"
Configuration="@(ResolvedRazorConfiguration)"
Extensions="@(ResolvedRazorExtension)"
Sources="@(_RazorComponentDeclarationSources)"
ProjectRoot="$(MSBuildProjectDirectory)"
TagHelperManifest="$(_RazorComponentDeclarationManifest)"
GenerateDeclaration="true" />
<ItemGroup>
<FileWrites Include="@(_RazorComponentDeclaration)" />
</ItemGroup>
<ItemGroup Condition="'$(DesignTimeBuild)'=='true'">
<Compile Include="@(_RazorComponentDeclaration)" />
</ItemGroup>
</Target>
<Target
Name="RazorCompileComponentDeclaration"
Inputs="
$(MSBuildAllProjects);
@(_RazorComponentDeclaration);
@(Compile);
$(AssemblyOriginatorKeyFile);
@(ReferencePathWithRefAssemblies);
@(CompiledLicenseFile);
@(LinkResource);
$(ResolvedCodeAnalysisRuleSet);
@(AdditionalFiles)"
Outputs="@(_RazorComponentDeclarationAssembly);$(NonExistentFile)"
Condition="'$(DesignTimeBuild)'!='true' AND '$(Language)'=='C#' AND '@(_RazorComponentDeclaration)' != ''">
<MakeDir Directories="%(_RazorComponentDeclarationAssembly.RelativeDir)" />
<!-- These two compiler warnings are raised when a reference is bound to a different version
than specified in the assembly reference version number. MSBuild raises the same warning in this case,
so the compiler warning would be redundant. -->
<PropertyGroup Condition="('$(TargetFrameworkVersion)' != 'v1.0') and ('$(TargetFrameworkVersion)' != 'v1.1')">
<NoWarn>$(NoWarn);1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup>
<!-- To match historical behavior, when inside VS11+ disable the warning from csc.exe indicating that no sources were passed in-->
<NoWarn Condition="'$(BuildingInsideVisualStudio)' == 'true' AND '$(VisualStudioVersion)' != '' AND '$(VisualStudioVersion)' &gt; '10.0'">$(NoWarn);2008</NoWarn>
</PropertyGroup>
<ItemGroup Condition="'$(TargetingClr2Framework)' == 'true'">
<ReferencePathWithRefAssemblies>
<EmbedInteropTypes />
</ReferencePathWithRefAssemblies>
</ItemGroup>
<PropertyGroup>
<!-- If the user has specified AppConfigForCompiler, we'll use it. If they have not, but they set UseAppConfigForCompiler,
then we'll use AppConfig -->
<AppConfigForCompiler Condition="'$(AppConfigForCompiler)' == '' AND '$(UseAppConfigForCompiler)' == 'true'">$(AppConfig)</AppConfigForCompiler>
</PropertyGroup>
<!-- Prefer32Bit was introduced in .NET 4.5. Set it to false if we are targeting 4.0 -->
<PropertyGroup Condition="('$(TargetFrameworkVersion)' == 'v4.0')">
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<!-- TODO: Remove this ItemGroup once it has been moved to "_GenerateCompileInputs" target in Microsoft.Common.CurrentVersion.targets.
https://github.com/dotnet/roslyn/issues/12223 -->
<ItemGroup Condition="('$(AdditionalFileItemNames)' != '')">
<AdditionalFileItems Include="$(AdditionalFileItemNames)" />
<AdditionalFiles Include="@(%(AdditionalFileItems.Identity))" />
</ItemGroup>
<PropertyGroup Condition="'$(UseSharedCompilation)' == ''">
<UseSharedCompilation>true</UseSharedCompilation>
</PropertyGroup>
<Csc
AdditionalLibPaths="$(AdditionalLibPaths)"
AddModules="@(AddModules)"
AdditionalFiles="@(AdditionalFiles)"
AllowUnsafeBlocks="$(AllowUnsafeBlocks)"
Analyzers="@(Analyzer)"
ApplicationConfiguration="$(AppConfigForCompiler)"
BaseAddress="$(BaseAddress)"
CheckForOverflowUnderflow="$(CheckForOverflowUnderflow)"
ChecksumAlgorithm="$(ChecksumAlgorithm)"
CodeAnalysisRuleSet="$(ResolvedCodeAnalysisRuleSet)"
CodePage="$(CodePage)"
DebugType="$(DebugType)"
DefineConstants="$(DefineConstants)"
DelaySign="$(DelaySign)"
DisabledWarnings="$(NoWarn)"
EmitDebugInformation="$(DebugSymbols)"
EnvironmentVariables="$(CscEnvironment)"
ErrorEndLocation="$(ErrorEndLocation)"
ErrorLog="$(ErrorLog)"
ErrorReport="$(ErrorReport)"
Features="$(Features)"
FileAlignment="$(FileAlignment)"
GenerateFullPaths="$(GenerateFullPaths)"
HighEntropyVA="$(HighEntropyVA)"
Instrument="$(Instrument)"
KeyContainer="$(KeyContainerName)"
KeyFile="$(KeyOriginatorFile)"
LangVersion="$(LangVersion)"
LinkResources="@(LinkResource)"
MainEntryPoint="$(StartupObject)"
ModuleAssemblyName="$(ModuleAssemblyName)"
NoConfig="true"
NoLogo="$(NoLogo)"
NoStandardLib="$(NoCompilerStandardLib)"
NoWin32Manifest="$(NoWin32Manifest)"
Optimize="$(Optimize)"
Deterministic="$(Deterministic)"
PublicSign="$(PublicSign)"
OutputAssembly="@(_RazorComponentDeclarationAssembly)"
Platform="$(PlatformTarget)"
Prefer32Bit="$(Prefer32Bit)"
PreferredUILang="$(PreferredUILang)"
ProvideCommandLineArgs="$(ProvideCommandLineArgs)"
References="@(ReferencePathWithRefAssemblies)"
ReportAnalyzer="$(ReportAnalyzer)"
Resources="@(CompiledLicenseFile)"
ResponseFiles="$(CompilerResponseFile)"
RuntimeMetadataVersion="$(RuntimeMetadataVersion)"
SharedCompilationId="$(SharedCompilationId)"
SkipCompilerExecution="$(SkipCompilerExecution)"
Sources="@(_RazorComponentDeclaration);@(Compile)"
SubsystemVersion="$(SubsystemVersion)"
TargetType="$(OutputType)"
ToolExe="$(CscToolExe)"
ToolPath="$(CscToolPath)"
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
UseHostCompilerIfAvailable="$(UseHostCompilerIfAvailable)"
UseSharedCompilation="$(UseSharedCompilation)"
Utf8Output="$(Utf8Output)"
VsSessionGuid="$(VsSessionGuid)"
WarningLevel="$(WarningLevel)"
WarningsAsErrors="$(WarningsAsErrors)"
WarningsNotAsErrors="$(WarningsNotAsErrors)"
PathMap="$(PathMap)"
SourceLink="$(SourceLink)">
<Output TaskParameter="CommandLineArgs" ItemName="CscCommandLineArgs" />
</Csc>
<ItemGroup>
<FileWrites Include="@(_RazorComponentDeclarationAssembly)" Condition="Exists('@(_RazorComponentDeclarationAssembly)')" />
</ItemGroup>
</Target>
<Target Name="_ResolveComponentRazorGenerateInputs">
<ItemGroup>
<!-- Add RazorComponent files to the set of files to be code-gened. -->
<RazorGenerateWithTargetPath Include="@(RazorComponentWithTargetPath)" />
</ItemGroup>
<!-- Point to the declaration assembly to perform taghelper discovery -->
<PropertyGroup>
<_RazorGenerateTargetReferenceAssembly>@(_RazorComponentDeclarationAssembly->Metadata('FullPath'))</_RazorGenerateTargetReferenceAssembly>
</PropertyGroup>
</Target>
<!--
Once RazorCompileComponentDeclaration is completed, we have all the inputs required for code generation of
RazorComponents and RazorGenerate items. We'll initialize the inputs for RazorGenerate and invoke it.
This should result in RazorGenerate \ RazorTagHelper targets no-oping when it get invoked as
part of PrepareForRun \ PrepareForPublish for non-components (.cshtml) files.
-->
<Target
Name="RazorGenerateComponentDefinition"
DependsOnTargets="_ResolveComponentRazorGenerateInputs;RazorGenerate"
Condition="'$(DesignTimeBuild)' != 'true' AND '@(_RazorComponentDefinition->Count())' != '0' AND Exists('@(_RazorComponentDeclarationAssembly)')">
<ItemGroup Condition="'$(DesignTimeBuild)' != 'true'">
<Compile Include="@(_RazorComponentDefinition)" />
</ItemGroup>
</Target>
<PropertyGroup>
<RazorComponentGenerateCoreDependsOn>
RazorGenerateComponentDeclaration;
RazorCompileComponentDeclaration;
RazorGenerateComponentDefinition;
</RazorComponentGenerateCoreDependsOn>
</PropertyGroup>
<Target Name="RazorComponentGenerateCore" DependsOnTargets="$(RazorComponentGenerateCoreDependsOn)">
</Target>
</Project>

View File

@ -29,6 +29,11 @@ Copyright (c) .NET Foundation. All rights reserved.
-->
<EnableDefaultRazorGenerateItems Condition="'$(EnableDefaultRazorGenerateItems)'==''">true</EnableDefaultRazorGenerateItems>
<!--
Set to true to automatically include Razor (.razor) files in @(RazorComponent) from @(Content).
-->
<EnableDefaultRazorComponentItems Condition="'$(EnableDefaultRazorComponentItems)'==''">true</EnableDefaultRazorComponentItems>
<!--
Set to true to copy RazorGenerate items (.cshtml) to the publish directory.
@ -61,14 +66,21 @@ Copyright (c) .NET Foundation. All rights reserved.
<!--
Configures the file extension used for generated C# files.
-->
<RazorGenerateOutputFileExtension>.g.cshtml.cs</RazorGenerateOutputFileExtension>
<RazorGenerateOutputFileExtension>.g.cs</RazorGenerateOutputFileExtension>
<!--
Configures the file extension used for the generated C# definition files.
-->
<RazorGenerateOutputDefinitionFileExtension>.g.i.cs</RazorGenerateOutputDefinitionFileExtension>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup Condition="'$(EnableDefaultItems)' == 'true' And '$(EnableDefaultContentItems)' == 'true'">
<Content Include="**\*.cshtml" CopyToPublishDirectory="PreserveNewest" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(DefaultWebContentItemExcludes)" />
<Content Include="**\*.razor" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(DefaultWebContentItemExcludes)" />
<None Remove="**\*.cshtml" />
<None Remove="**\*.razor" />
</ItemGroup>
</Project>

View File

@ -50,12 +50,23 @@ Copyright (c) .NET Foundation. All rights reserved.
ResolveTagHelperRazorGenerateInputs
</PrepareForRazorGenerateDependsOn>
<PrepareForRazorComponentGenerateDependsOn>
ResolveRazorConfiguration;
ResolveRazorComponentInputs;
AssignRazorComponentTargetPaths;
</PrepareForRazorComponentGenerateDependsOn>
<RazorGenerateDependsOn>
PrepareForRazorGenerate;
_CheckForMissingRazorCompiler;
RazorCoreGenerate
</RazorGenerateDependsOn>
<RazorComponentGenerateDependsOn>
PrepareForRazorComponentGenerate;
RazorComponentGenerateCore
</RazorComponentGenerateDependsOn>
<PrepareForRazorCompileDependsOn>
RazorGenerate;
ResolveRazorCompileInputs;
@ -81,6 +92,11 @@ Copyright (c) .NET Foundation. All rights reserved.
_RazorAddDebugSymbolsProjectOutputGroupOutput
</DebugSymbolsProjectOutputGroupDependsOn>
<CoreCompileDependsOn>
RazorComponentGenerate;
$(CoreCompileDependsOn)
</CoreCompileDependsOn>
<PrepareForBuildDependsOn>
$(PrepareForBuildDependsOn);
ResolveRazorGenerateInputs
@ -128,6 +144,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<!-- Output directory used for generated files -->
<RazorGenerateIntermediateOutputPath Condition="'$(RazorGenerateIntermediateOutputPath)'==''">$(IntermediateOutputPath)Razor\</RazorGenerateIntermediateOutputPath>
<_RazorComponentDeclarationOutputPath Condition="'$(_RazorComponentDeclarationOutputPath)'==''">$(IntermediateOutputPath)RazorDeclaration\</_RazorComponentDeclarationOutputPath>
<!--
Use the suffix .Views when producing compiled view assemblies. This matches the requirements for Mvc's ViewsFeatureProvider.
-->
@ -135,11 +153,11 @@ Copyright (c) .NET Foundation. All rights reserved.
<!-- Suffix appended to $(TargetName) to produce $(RazorTargetName), the name of the assembly produced by Razor -->
<RazorTargetNameSuffix Condition="'$(RazorTargetNameSuffix)' == ''">.Razor</RazorTargetNameSuffix>
<!-- File name (without extension) of the assembly produced by Razor -->
<RazorTargetName Condition="'$(RazorTargetName)'==''">$(TargetName)$(RazorTargetNameSuffix)</RazorTargetName>
<!--
<!--
The compatibility zone - these properties were provided by the MVC Precompilation tool and they
map to supported settings in Razor SDK.
@ -210,7 +228,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<ItemGroup>
<!-- Used to creating the final compiled Razor dll -->
<RazorIntermediateAssembly Condition="'$(RazorIntermediateAssembly)'==''" Include="$(IntermediateOutputPath)$(RazorTargetName).dll" />
<!-- Used in Compilation.targets -->
<!-- Used in Compilation.targets -->
<_RazorDebugSymbolsIntermediatePath Condition="'$(_RazorDebugSymbolsProduced)'=='true'" Include="$(IntermediateOutputPath)$(RazorTargetName).pdb" />
</ItemGroup>
@ -246,6 +264,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<!-- For projects targeting 3.x and later, use the compiler that ships in the Sdk -->
<Import Project="Microsoft.NET.Sdk.Razor.CodeGeneration.targets" Condition="'$(_EnableAllInclusiveRazorSdk)' == 'true'" />
<Import Project="Microsoft.NET.Sdk.Razor.Component.targets" />
<Import Project="Microsoft.NET.Sdk.Razor.GenerateAssemblyInfo.targets" />
<!--
@ -258,9 +278,15 @@ Copyright (c) .NET Foundation. All rights reserved.
<Target Name="PrepareForRazorGenerate" DependsOnTargets="$(PrepareForRazorGenerateDependsOn)">
</Target>
<Target Name="PrepareForRazorComponentGenerate" DependsOnTargets="$(PrepareForRazorComponentGenerateDependsOn)">
</Target>
<Target Name="RazorGenerate" DependsOnTargets="$(RazorGenerateDependsOn)">
</Target>
<Target Name="RazorComponentGenerate" DependsOnTargets="$(RazorComponentGenerateDependsOn)">
</Target>
<Target Name="PrepareForRazorCompile" DependsOnTargets="$(PrepareForRazorCompileDependsOn)">
</Target>
@ -271,7 +297,7 @@ Copyright (c) .NET Foundation. All rights reserved.
Computes the applicable @(ResolvedRazorConfiguration) and @(ResolvedRazorExtension) items that match the project's
configuration.
-->
<Target
<Target
Name="ResolveRazorConfiguration"
Condition="'$(RazorDefaultConfiguration)'!=''">
@ -299,7 +325,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<_GenerateRazorAssemblyInfoDependsOn>RazorGetAssemblyAttributes;$(_GenerateRazorAssemblyInfoDependsOn)</_GenerateRazorAssemblyInfoDependsOn>
</PropertyGroup>
<Target
<Target
Name="RazorGetAssemblyAttributes"
Condition="'$(GenerateRazorHostingAssemblyInfo)'=='true' and '$(RazorDefaultConfiguration)'!=''"
DependsOnTargets="ResolveRazorConfiguration">
@ -354,7 +380,7 @@ Copyright (c) .NET Foundation. All rights reserved.
'$(CopyRazorGenerateFilesToPublishDirectory)'=='false' and
'$(ResolvedRazorCompileToolset)'=='RazorSdk' and
'$(RazorCompileOnPublish)'=='true'">
<Content Condition="'%(Content.Extension)'=='.cshtml'" CopyToPublishDirectory="Never" />
</ItemGroup>
@ -366,6 +392,40 @@ Copyright (c) .NET Foundation. All rights reserved.
</ItemGroup>
</Target>
<Target Name="ResolveRazorComponentInputs">
<!--
Gathers input source files for Razor component generation. This is a separate target so that we can avoid
lots of work when there are no inputs for code generation.
NOTE: This target is called as part of an incremental build scenario in VS. Do not perform any work
outside of calculating RazorComponent items in this target.
-->
<ItemGroup Condition="'$(EnableDefaultRazorComponentItems)'=='true'">
<RazorComponent Include="@(Content)" Condition="'%(Content.Extension)'=='.razor'" />
</ItemGroup>
</Target>
<Target Name="AssignRazorComponentTargetPaths" Condition="'@(RazorComponent)' != ''">
<AssignTargetPath Files="@(RazorComponent)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="RazorComponentWithTargetPath" />
</AssignTargetPath>
<ItemGroup>
<RazorComponentWithTargetPath Condition="'%(RazorComponentWithTargetPath.GeneratedOutput)' == ''">
<GeneratedOutput>$(RazorGenerateIntermediateOutputPath)%(RazorComponentWithTargetPath.TargetPath)$(RazorGenerateOutputFileExtension)</GeneratedOutput>
</RazorComponentWithTargetPath>
<RazorComponentWithTargetPath Condition="'%(RazorComponentWithTargetPath.GeneratedDeclaration)' == ''">
<GeneratedDeclaration>$(_RazorComponentDeclarationOutputPath)%(RazorComponentWithTargetPath.TargetPath)$(RazorGenerateOutputFileExtension)</GeneratedDeclaration>
</RazorComponentWithTargetPath>
<RazorComponentWithTargetPath Condition="'%(RazorComponentWithTargetPath.DocumentKind)' == ''">
<DocumentKind>component</DocumentKind>
</RazorComponentWithTargetPath>
</ItemGroup>
</Target>
<Target Name="AssignRazorGenerateTargetPaths" Condition="'@(RazorGenerate)' != ''">
<AssignTargetPath Files="@(RazorGenerate)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="RazorGenerateWithTargetPath" />
@ -373,7 +433,11 @@ Copyright (c) .NET Foundation. All rights reserved.
<ItemGroup>
<RazorGenerateWithTargetPath Condition="'%(RazorGenerateWithTargetPath.GeneratedOutput)' == ''">
<GeneratedOutput>$(RazorGenerateIntermediateOutputPath)$([System.IO.Path]::ChangeExtension('%(RazorGenerateWithTargetPath.TargetPath)', '$(RazorGenerateOutputFileExtension)'))</GeneratedOutput>
<GeneratedOutput>$(RazorGenerateIntermediateOutputPath)%(RazorGenerateWithTargetPath.TargetPath)$(RazorGenerateOutputFileExtension)</GeneratedOutput>
</RazorGenerateWithTargetPath>
<RazorGenerateWithTargetPath Condition="'%(RazorGenerateWithTargetPath.DocumentKind)' == ''">
<DocumentKind>mvc</DocumentKind>
</RazorGenerateWithTargetPath>
</ItemGroup>
</Target>
@ -386,8 +450,20 @@ Copyright (c) .NET Foundation. All rights reserved.
DependsOnTargets="ResolveReferences">
<ItemGroup>
<RazorReferencePath Include="@(ReferencePath)"/>
<RazorReferencePath Include="$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)$(TargetName)$(TargetExt)'))"/>
</ItemGroup>
<PropertyGroup>
<!--
Assembly of the project used as input in RazorGenerate. This property is overwritten to point to the definition assembly when components are present.
-->
<_RazorGenerateTargetReferenceAssembly Condition="'$(_RazorGenerateTargetReferenceAssembly)'==''">
@(IntermediateAssembly->Metadata('FullPath'))
</_RazorGenerateTargetReferenceAssembly>
<_RazorCompileTargetReferenceAssembly Condition="'$(_RazorCompileTargetReferenceAssembly)'==''">
@(IntermediateAssembly->Metadata('FullPath'))
</_RazorCompileTargetReferenceAssembly>
</PropertyGroup>
</Target>
<!--
@ -418,7 +494,7 @@ Copyright (c) .NET Foundation. All rights reserved.
This target just hooks up other targets since Publish and PrepareForPublish don't have a DependsOnTargets
property we can use.
-->
<Target
<Target
Name="_RazorPrepareForPublish"
AfterTargets="PrepareForPublish"
DependsOnTargets="RazorCompile"
@ -428,7 +504,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<!--
This target adds the Razor assembly to the BuiltProjectOutputGroupOutput - which is used as input to the Pack target.
-->
<Target
<Target
Name="_RazorAddBuiltProjectOutputGroupOutput"
DependsOnTargets="_ResolveRazorTargetPath;ResolveRazorGenerateInputs"
Condition="'$(ResolvedRazorCompileToolset)'=='RazorSdk' and '$(RazorCompileOnBuild)'=='true'">
@ -447,7 +523,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<ItemGroup Condition="Exists('@(_RazorDebugSymbolsIntermediatePath)')">
<DebugSymbolsProjectOutputGroupOutput Include="%(_RazorDebugSymbolsIntermediatePath.FullPath)" FinalOutputPath="$(RazorTargetDir)$(RazorTargetName).pdb" />
</ItemGroup>
</Target>
<!--
@ -463,7 +539,7 @@ Copyright (c) .NET Foundation. All rights reserved.
Called as part of GetCopyToOutputDirectoryItems - this target populates the list of items that get
copied to the output directory when building as a project reference.
-->
<Target
<Target
Name="_RazorGetCopyToOutputDirectoryItems"
DependsOnTargets="ResolveRazorGenerateInputs"
Condition="'$(ResolvedRazorCompileToolset)'=='RazorSdk' and '$(RazorCompileOnBuild)'=='true'">
@ -519,8 +595,8 @@ Copyright (c) .NET Foundation. All rights reserved.
Called as part of CopyFilesToOutputDirectory - this target is called when building the project to copy
files to the output directory.
-->
<Target
Name="_RazorCopyFilesToOutputDirectory"
<Target
Name="_RazorCopyFilesToOutputDirectory"
DependsOnTargets="_ResolveRazorTargetPath;RazorCompile"
AfterTargets="CopyFilesToOutputDirectory"
Condition="'$(ResolvedRazorCompileToolset)'=='RazorSdk' and '$(RazorCompileOnBuild)'=='true'">
@ -541,9 +617,9 @@ Copyright (c) .NET Foundation. All rights reserved.
<Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>
</Copy>
<Message
Importance="High"
Text="$(MSBuildProjectName) -&gt; @(_RazorAssembly->'%(FullPath)')"
<Message
Importance="High"
Text="$(MSBuildProjectName) -&gt; @(_RazorAssembly->'%(FullPath)')"
Condition="Exists('@(RazorIntermediateAssembly)') and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" />
<!-- Copy the Razor debug information file (.pdb), if any -->
@ -569,7 +645,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<Touch Files="@(CopyUpToDateMarker)" AlwaysCreate="true" Condition="'@(_RazorAssembly)' != ''">
<Output TaskParameter="TouchedFiles" ItemName="FileWrites" />
</Touch>
</Target>
<!--
@ -609,7 +685,7 @@ Copyright (c) .NET Foundation. All rights reserved.
most usages of Razor. There's no setting that excludes just the ref assemblies, so we do it ourselves.
-->
<ItemGroup>
<ResolvedFileToPublish
<ResolvedFileToPublish
Remove="%(ResolvedFileToPublish.Identity)"
Condition="'%(ResolvedFileToPublish.RelativePath)'=='$(RefAssembliesFolderName)\%(Filename)%(Extension)'"/>
</ItemGroup>

View File

@ -61,7 +61,6 @@ Examples:
@class.Modifiers.Add("internal");
});
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);

View File

@ -194,22 +194,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
}
}
return codeDocument.GetDocumentIntermediateNode();
}
private string GetCSharpContent(IntermediateNode node)
{
var builder = new StringBuilder();
for (var i = 0; i < node.Children.Count; i++)
{
var child = node.Children[i] as IntermediateToken;
if (child.Kind == TokenKind.CSharp)
{
builder.Append(child.Content);
}
}
return builder.ToString();
var irDocument = codeDocument.GetDocumentIntermediateNode();
irDocument.DocumentKind = MvcViewDocumentClassifierPass.MvcViewDocumentKind;
return irDocument;
}
private class ClassNodeVisitor : IntermediateNodeWalker

View File

@ -1,118 +0,0 @@
#pragma checksum "TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "78993008d95836bec2b9175d4294bf7bd5f5f109"
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(Razor.Template), @"default", @"/TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml")]
namespace Razor
{
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"78993008d95836bec2b9175d4294bf7bd5f5f109", @"/TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml")]
public class Template
{
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_0 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("value", "Hello", global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_1 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("type", new global::Microsoft.AspNetCore.Html.HtmlString("text"), global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.SingleQuotes);
private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute __tagHelperAttribute_2 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute("unbound", new global::Microsoft.AspNetCore.Html.HtmlString("foo"), global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
#line hidden
#pragma warning disable 0169
private string __tagHelperStringValueBuffer;
#pragma warning restore 0169
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext;
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner();
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __backed__tagHelperScopeManager = null;
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __tagHelperScopeManager
{
get
{
if (__backed__tagHelperScopeManager == null)
{
__backed__tagHelperScopeManager = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager(StartTagHelperWritingScope, EndTagHelperWritingScope);
}
return __backed__tagHelperScopeManager;
}
}
private global::FormTagHelper __FormTagHelper;
private global::InputTagHelper __InputTagHelper;
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
BeginContext(31, 28, true);
WriteLiteral("<span someattr>Hola</span>\r\n");
EndContext();
BeginContext(61, 7, false);
#line 3 "TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml"
Write("Hello");
#line default
#line hidden
EndContext();
BeginContext(69, 2, true);
WriteLiteral("\r\n");
EndContext();
BeginContext(71, 87, false);
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("form", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
BeginContext(91, 6, true);
WriteLiteral("\r\n ");
EndContext();
BeginContext(97, 52, false);
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, "test", async() => {
}
);
__InputTagHelper = CreateTagHelper<global::InputTagHelper>();
__tagHelperExecutionContext.Add(__InputTagHelper);
__InputTagHelper.FooProp = (string)__tagHelperAttribute_0.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_0);
#line 5 "TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml"
__InputTagHelper.BarProp = DateTime.Now;
#line default
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute("date", __InputTagHelper.BarProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_1);
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
if (!__tagHelperExecutionContext.Output.IsContentModified)
{
await __tagHelperExecutionContext.SetOutputContentAsync();
}
Write(__tagHelperExecutionContext.Output);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
EndContext();
BeginContext(149, 2, true);
WriteLiteral("\r\n");
EndContext();
}
);
__FormTagHelper = CreateTagHelper<global::FormTagHelper>();
__tagHelperExecutionContext.Add(__FormTagHelper);
__tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_2);
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
if (!__tagHelperExecutionContext.Output.IsContentModified)
{
await __tagHelperExecutionContext.SetOutputContentAsync();
}
Write(__tagHelperExecutionContext.Output);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
EndContext();
BeginContext(158, 31, true);
WriteLiteral("\r\n\r\n<span>Here is some content ");
EndContext();
BeginContext(207, 9, true);
WriteLiteral("</span>\r\n");
EndContext();
BeginContext(217, 29, false);
#line 9 "TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml"
Write(Foo(item => new Template(async(__razor_template_writer) => {
PushWriter(__razor_template_writer);
BeginContext(222, 24, true);
WriteLiteral("<span>Hello world</span>");
EndContext();
PopWriter();
}
)));
#line default
#line hidden
EndContext();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591

View File

@ -1,9 +0,0 @@
@addTagHelper *, TestAssembly
<span someattr>Hola</span>
@("Hello")
<form unbound="foo">
<input value=Hello date=@DateTime.Now type='text' />
</form>
<span>Here is some content @*with a comment*@</span>
@Foo(@<span>Hello world</span>)

View File

@ -1,100 +0,0 @@
Document -
RazorCompiledItemAttribute -
NamespaceDeclaration - - Razor
RazorSourceChecksumAttribute -
ClassDeclaration - - public - Template - -
PreallocatedTagHelperPropertyValue - - __tagHelperAttribute_0 - value - Hello - HtmlAttributeValueStyle.DoubleQuotes
PreallocatedTagHelperHtmlAttributeValue - - __tagHelperAttribute_1 - type - text - HtmlAttributeValueStyle.SingleQuotes
PreallocatedTagHelperHtmlAttributeValue - - __tagHelperAttribute_2 - unbound - foo - HtmlAttributeValueStyle.DoubleQuotes
DefaultTagHelperRuntime -
FieldDeclaration - - private - global::FormTagHelper - __FormTagHelper
FieldDeclaration - - private - global::InputTagHelper - __InputTagHelper
MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync
CSharpCode -
IntermediateToken - - CSharp - BeginContext(31, 28, true);
HtmlContent - (31:1,0 [28] BasicTest.cshtml)
IntermediateToken - (31:1,0 [5] BasicTest.cshtml) - Html - <span
IntermediateToken - (36:1,5 [9] BasicTest.cshtml) - Html - someattr
IntermediateToken - (45:1,14 [1] BasicTest.cshtml) - Html - >
IntermediateToken - (46:1,15 [4] BasicTest.cshtml) - Html - Hola
IntermediateToken - (50:1,19 [7] BasicTest.cshtml) - Html - </span>
IntermediateToken - (57:1,26 [2] BasicTest.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(61, 7, false);
CSharpExpression - (61:2,2 [7] BasicTest.cshtml)
IntermediateToken - (61:2,2 [7] BasicTest.cshtml) - CSharp - "Hello"
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(69, 2, true);
HtmlContent - (69:2,10 [2] BasicTest.cshtml)
IntermediateToken - (69:2,10 [2] BasicTest.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(71, 87, false);
TagHelper - (71:3,0 [87] BasicTest.cshtml) - form - TagMode.StartTagAndEndTag
DefaultTagHelperBody -
CSharpCode -
IntermediateToken - - CSharp - BeginContext(91, 6, true);
HtmlContent - (91:3,20 [6] BasicTest.cshtml)
IntermediateToken - (91:3,20 [6] BasicTest.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(97, 52, false);
TagHelper - (97:4,4 [52] BasicTest.cshtml) - input - TagMode.SelfClosing
DefaultTagHelperBody -
DefaultTagHelperCreate - - InputTagHelper
PreallocatedTagHelperProperty - (110:4,17 [5] BasicTest.cshtml) - __tagHelperAttribute_0 - value - FooProp
DefaultTagHelperProperty - (121:4,28 [13] BasicTest.cshtml) - date - System.DateTime InputTagHelper.BarProp - HtmlAttributeValueStyle.DoubleQuotes
CSharpExpression - (122:4,29 [12] BasicTest.cshtml)
IntermediateToken - (122:4,29 [12] BasicTest.cshtml) - CSharp - DateTime.Now
PreallocatedTagHelperHtmlAttribute - - __tagHelperAttribute_1
DefaultTagHelperExecute -
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(149, 2, true);
HtmlContent - (149:4,56 [2] BasicTest.cshtml)
IntermediateToken - (149:4,56 [2] BasicTest.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
DefaultTagHelperCreate - - FormTagHelper
PreallocatedTagHelperHtmlAttribute - - __tagHelperAttribute_2
DefaultTagHelperExecute -
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(158, 31, true);
HtmlContent - (158:5,7 [31] BasicTest.cshtml)
IntermediateToken - (158:5,7 [4] BasicTest.cshtml) - Html - \n\n
IntermediateToken - (162:7,0 [6] BasicTest.cshtml) - Html - <span>
IntermediateToken - (168:7,6 [21] BasicTest.cshtml) - Html - Here is some content
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(207, 9, true);
HtmlContent - (207:7,45 [9] BasicTest.cshtml)
IntermediateToken - (207:7,45 [7] BasicTest.cshtml) - Html - </span>
IntermediateToken - (214:7,52 [2] BasicTest.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(217, 29, false);
CSharpExpression - (217:8,1 [29] BasicTest.cshtml)
IntermediateToken - (217:8,1 [4] BasicTest.cshtml) - CSharp - Foo(
Template - (222:8,6 [24] BasicTest.cshtml)
CSharpCode -
IntermediateToken - - CSharp - BeginContext(222, 24, true);
HtmlContent - (222:8,6 [24] BasicTest.cshtml)
IntermediateToken - (222:8,6 [6] BasicTest.cshtml) - Html - <span>
IntermediateToken - (228:8,12 [11] BasicTest.cshtml) - Html - Hello world
IntermediateToken - (239:8,23 [7] BasicTest.cshtml) - Html - </span>
CSharpCode -
IntermediateToken - - CSharp - EndContext();
IntermediateToken - (246:8,30 [1] BasicTest.cshtml) - CSharp - )
CSharpCode -
IntermediateToken - - CSharp - EndContext();

View File

@ -0,0 +1,207 @@
// 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.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
public class ComponentDocumentClassifierPassTest
{
[Fact]
public void Execute_SetsDocumentKind()
{
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", "Test.razor"));
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal(ComponentDocumentClassifierPass.ComponentDocumentKind, irDocument.DocumentKind);
}
[Fact]
public void ComponentDocumentClassifierPass_SetsNamespace()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: "/MyApp/Test.razor", relativePath: "Test.razor");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal("MyApp", visitor.Namespace.Content);
}
[Fact]
public void ComponentDocumentClassifierPass_SetsClass()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: "/MyApp/Test.razor", relativePath: "Test.razor");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal($"global::{CodeGenerationConstants.RazorComponent.FullTypeName}", visitor.Class.BaseType);
Assert.Equal(new[] { "public", "sealed" }, visitor.Class.Modifiers);
Assert.Equal("Test", visitor.Class.ClassName);
}
[Fact]
public void ComponentDocumentClassifierPass_UsesRelativePathToGenerateTypeNameAndNamespace()
{
// Arrange
var relativePath = "/Pages/Announcements/Banner.razor";
var properties = new RazorSourceDocumentProperties(filePath: $"/MyApp{relativePath}", relativePath: relativePath);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal("Banner", visitor.Class.ClassName);
Assert.Equal("MyApp.Pages.Announcements", visitor.Namespace.Content);
}
[Fact]
public void ComponentDocumentClassifierPass_SanitizesClassName()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: @"x:\path.with+invalid-chars.razor", relativePath: "path.with+invalid-chars.razor");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal("path_with_invalid_chars", visitor.Class.ClassName);
}
[Fact]
public void ComponentDocumentClassifierPass_SetsUpMainMethod()
{
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", "Test.razor"));
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal(CodeGenerationConstants.RazorComponent.BuildRenderTree, visitor.Method.MethodName);
Assert.Equal("void", visitor.Method.ReturnType);
Assert.Equal(new[] { "public", "override" }, visitor.Method.Modifiers);
}
private static RazorProjectEngine CreateProjectEngine() => RazorProjectEngine.Create();
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);
}
}
private class TestComponentDocumentClassifierPass : ComponentDocumentClassifierPass
{
public new bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
=> base.IsMatch(codeDocument, documentNode);
}
}
}

View File

@ -465,7 +465,6 @@ namespace Microsoft.AspNetCore.Razor.Language
{
builder?.Invoke(b);
FunctionsDirective.Register(b);
SectionDirective.Register(b);
b.AddTagHelpers(tagHelpers);

View File

@ -76,10 +76,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
private static RazorProjectEngine CreateProjectEngine()
{
return RazorProjectEngine.Create(b =>
{
FunctionsDirective.Register(b);
});
return RazorProjectEngine.Create();
}
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine)

View File

@ -884,7 +884,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
// Some of these tests use templates
builder.AddTargetExtension(new TemplateTargetExtension());
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
});
@ -909,8 +908,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
// Some of these tests use templates
builder.AddTargetExtension(new TemplateTargetExtension());
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
});
@ -935,7 +933,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
// Some of these tests use templates
builder.AddTargetExtension(new TemplateTargetExtension());
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
});
@ -961,7 +958,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
// Some of these tests use templates
builder.AddTargetExtension(new TemplateTargetExtension());
FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
});

View File

@ -0,0 +1,50 @@
// 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.Components;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentIntegrationTest : IntegrationTestBase
{
public ComponentIntegrationTest()
: base(generateBaselines: null)
{
Configuration = RazorConfiguration.Default;
FileExtension = ".razor";
}
protected override RazorConfiguration Configuration { get; }
[Fact]
public void BasicTest()
{
var projectEngine = CreateProjectEngine(engine =>
{
engine.Features.Add(new InputDocumentKindClassifierPass());
});
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
AssertCSharpDocumentMatchesBaseline(codeDocument.GetCSharpDocument());
}
private class InputDocumentKindClassifierPass : RazorEngineFeatureBase, IRazorDocumentClassifierPass
{
// Run before other document classifiers
public int Order => -1000;
public void Execute(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
}
}
}
}

View File

@ -0,0 +1,65 @@
// 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.Components;
using Microsoft.AspNetCore.Razor.Language.IntegrationTests;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class SuppressPrimaryMethodBodyIntegrationTest : IntegrationTestBase
{
public SuppressPrimaryMethodBodyIntegrationTest()
: base(generateBaselines: null)
{
Configuration = RazorConfiguration.Default;
FileExtension = ".razor";
}
protected override RazorConfiguration Configuration { get; }
[Fact]
public void BasicTest()
{
var engine = CreateProjectEngine(e =>
{
e.Features.Add(new SetSuppressPrimaryMethodBodyFeature());
e.Features.Add(new InputDocumentKindClassifierPass());
});
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = engine.Process(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
var csharpDocument = codeDocument.GetCSharpDocument();
AssertCSharpDocumentMatchesBaseline(csharpDocument);
Assert.Empty(csharpDocument.Diagnostics);
}
private class SetSuppressPrimaryMethodBodyFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
options.SuppressPrimaryMethodBody = true;
}
}
private class InputDocumentKindClassifierPass : RazorEngineFeatureBase, IRazorDocumentClassifierPass
{
// Run before other document classifiers
public int Order => -1000;
public void Execute(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
codeDocument.SetInputDocumentKind(InputDocumentKind.Component);
}
}
}
}

View File

@ -1,8 +1,8 @@
// 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 Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Moq;
using Xunit;
@ -45,6 +45,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
var features = engine.EngineFeatures.OrderBy(f => f.GetType().Name).ToArray();
Assert.Collection(
features,
feature => Assert.IsType<ComponentDocumentClassifierPass>(feature),
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDocumentClassifierPass>(feature),
feature => Assert.IsType<DefaultDocumentClassifierPassFeature>(feature),
@ -56,6 +57,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
feature => Assert.IsType<DefaultTagHelperOptimizationPass>(feature),
feature => Assert.IsType<DesignTimeDirectivePass>(feature),
feature => Assert.IsType<DirectiveRemovalOptimizationPass>(feature),
feature => Assert.IsType<EliminateMethodBodyPass>(feature),
feature => Assert.IsType<FunctionsDirectivePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<MetadataAttributePass>(feature),
feature => Assert.IsType<PreallocatedTagHelperAttributeOptimizationPass>(feature));
@ -65,7 +68,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
{
var feature = engine.EngineFeatures.OfType<IRazorDirectiveFeature>().FirstOrDefault();
Assert.NotNull(feature);
Assert.Empty(feature.Directives);
Assert.Collection(
feature.Directives,
directive => Assert.Same(FunctionsDirective.Directive, directive));
}
private static void AssertDefaultTargetExtensions(RazorProjectEngine engine)

View File

@ -0,0 +1,26 @@
#pragma checksum "TestFiles/IntegrationTests/ComponentIntegrationTest/BasicTest.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "47f616cdc7ec75674c74246619b0e6118ef227e4"
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCore.BasicTest), @"component.1.0", @"/TestFiles/IntegrationTests/ComponentIntegrationTest/BasicTest.razor")]
namespace AspNetCore
{
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"47f616cdc7ec75674c74246619b0e6118ef227e4", @"/TestFiles/IntegrationTests/ComponentIntegrationTest/BasicTest.razor")]
public sealed class BasicTest : global::Microsoft.AspNetCore.Components.Component
{
#pragma warning disable 1998
public override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
WriteLiteral("Hello world\r\n<span class=\"content\"></span>\r\n\r\n");
}
#pragma warning restore 1998
#line 4 "TestFiles/IntegrationTests/ComponentIntegrationTest/BasicTest.razor"
string SomeProperty { get; set; }
#line default
#line hidden
}
}
#pragma warning restore 1591

View File

@ -0,0 +1,17 @@
Document -
RazorCompiledItemAttribute -
NamespaceDeclaration - - AspNetCore
RazorSourceChecksumAttribute -
ClassDeclaration - - public sealed - BasicTest - global::Microsoft.AspNetCore.Components.Component -
MethodDeclaration - - public override - void - BuildRenderTree
CSharpCode -
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
HtmlContent - (0:0,0 [46] BasicTest.razor)
IntermediateToken - (0:0,0 [13] BasicTest.razor) - Html - Hello world\n
IntermediateToken - (13:1,0 [5] BasicTest.razor) - Html - <span
IntermediateToken - (18:1,5 [16] BasicTest.razor) - Html - class="content"
IntermediateToken - (34:1,21 [1] BasicTest.razor) - Html - >
IntermediateToken - (35:1,22 [7] BasicTest.razor) - Html - </span>
IntermediateToken - (42:1,29 [4] BasicTest.razor) - Html - \n\n
CSharpCode - (58:3,12 [41] BasicTest.razor)
IntermediateToken - (58:3,12 [41] BasicTest.razor) - CSharp - \n string SomeProperty { get; set; }\n

View File

@ -0,0 +1,6 @@
Hello world
<span class="content"></span>
@functions {
string SomeProperty { get; set; }
}

View File

@ -0,0 +1,28 @@
#pragma checksum "TestFiles/IntegrationTests/SuppressPrimaryMethodBodyIntegrationTest/BasicTest.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "47f616cdc7ec75674c74246619b0e6118ef227e4"
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCore.BasicTest), @"component.1.0", @"/TestFiles/IntegrationTests/SuppressPrimaryMethodBodyIntegrationTest/BasicTest.razor")]
#pragma warning disable 0414
#pragma warning disable 0649
#pragma warning disable 0169
namespace AspNetCore
{
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"47f616cdc7ec75674c74246619b0e6118ef227e4", @"/TestFiles/IntegrationTests/SuppressPrimaryMethodBodyIntegrationTest/BasicTest.razor")]
public sealed class BasicTest : global::Microsoft.AspNetCore.Components.Component
{
#pragma warning disable 1998
public override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
}
#pragma warning restore 1998
#line 4 "TestFiles/IntegrationTests/SuppressPrimaryMethodBodyIntegrationTest/BasicTest.razor"
string SomeProperty { get; set; }
#line default
#line hidden
}
}
#pragma warning restore 1591

View File

@ -0,0 +1,12 @@
Document -
RazorCompiledItemAttribute -
CSharpCode -
IntermediateToken - - CSharp - #pragma warning disable 0414\n
IntermediateToken - - CSharp - #pragma warning disable 0649\n
IntermediateToken - - CSharp - #pragma warning disable 0169\n
NamespaceDeclaration - - AspNetCore
RazorSourceChecksumAttribute -
ClassDeclaration - - public sealed - BasicTest - global::Microsoft.AspNetCore.Components.Component -
MethodDeclaration - - public override - void - BuildRenderTree
CSharpCode - (58:3,12 [41] BasicTest.razor)
IntermediateToken - (58:3,12 [41] BasicTest.razor) - CSharp - \n string SomeProperty { get; set; }\n

View File

@ -0,0 +1,6 @@
Hello world
<span class="content"></span>
@functions {
string SomeProperty { get; set; }
}

View File

@ -132,6 +132,8 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
#endif
}
public string FileExtension { get; set; } = ".cshtml";
protected virtual void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
{
}
@ -193,7 +195,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
var suffixIndex = FileName.LastIndexOf("_");
var normalizedFileName = suffixIndex == -1 ? FileName : FileName.Substring(0, suffixIndex);
var sourceFileName = Path.ChangeExtension(normalizedFileName, ".cshtml");
var sourceFileName = Path.ChangeExtension(normalizedFileName, FileExtension);
var testFile = TestFile.Create(sourceFileName, GetType().GetTypeInfo().Assembly);
if (!testFile.Exists())
{

View File

@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\Microsoft.AspNetCore.Razor.Test.MvcShim\Microsoft.AspNetCore.Mvc*\**\*.cs" />
<Compile Include="..\Microsoft.AspNetCore.Razor.Test.MvcShim\Microsoft.*\**\*.cs" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
// 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.Components.RenderTree;
namespace Microsoft.AspNetCore.Components
{
public abstract class Component : IComponent
{
public virtual void BuildRenderTree(RenderTreeBuilder builder)
{
}
protected void WriteLiteral(string literal) { }
}
}

View File

@ -0,0 +1,9 @@
// 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.Components
{
public interface IComponent
{
}
}

View File

@ -0,0 +1,9 @@
// 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.Components.RenderTree
{
public abstract class RenderTreeBuilder
{
}
}

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<DefaultItemExcludes>$(DefaultItemExcludes);TestFiles\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>

View File

@ -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.IO;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace Microsoft.AspNetCore.Razor.Tools
{
internal static class MvcShim
{
public static readonly string AssemblyName = "Microsoft.AspNetCore.Razor.Test.MvcShim";
private static Assembly _assembly;
private static CSharpCompilation _baseCompilation;
public static Assembly Assembly
{
get
{
if (_assembly == null)
{
var filePath = Path.Combine(Directory.GetCurrentDirectory(), AssemblyName + ".dll");
_assembly = Assembly.LoadFrom(filePath);
}
return _assembly;
}
}
public static CSharpCompilation BaseCompilation
{
get
{
if (_baseCompilation == null)
{
_baseCompilation = TestCompilation.Create(Assembly);
}
return _baseCompilation;
}
}
}
}

View File

@ -4,6 +4,9 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Text.RegularExpressions;
@ -373,6 +376,28 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
public static void AssemblyContainsType(MSBuildResult result, string assemblyPath, string fullTypeName)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath));
using (var file = File.OpenRead(assemblyPath))
{
var peReader = new PEReader(file);
var metadataReader = peReader.GetMetadataReader();
var typeNames = metadataReader.TypeDefinitions.Where(t => !t.IsNil).Select(t =>
{
var type = metadataReader.GetTypeDefinition(t);
return metadataReader.GetString(type.Namespace) + "." + metadataReader.GetString(type.Name);
});
Assert.Contains(fullTypeName, typeNames);
}
}
private abstract class MSBuildXunitException : Xunit.Sdk.XunitException
{
protected MSBuildXunitException(MSBuildResult result)

View File

@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
var result = await DotnetMSBuild("Build");
var file = Path.Combine(Project.DirectoryPath, "SimpleTagHelper.cs");
var tagHelperOutputCache = Path.Combine(IntermediateOutputPath, "SimpleMvc.TagHelpers.output.cache");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.g.cshtml.cs");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs");
// Assert - 1
Assert.BuildPassed(result);
@ -133,6 +133,152 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
[Fact]
[InitializeTestProject("MvcWithComponents")]
public async Task BuildComponents_ErrorInGeneratedCode_ReportsMSBuildError_OnIncrementalBuild()
{
// Introducing a Razor semantic error
ReplaceContent("@{ // Unterminated code block", "Views", "Shared", "NavMenu.razor");
// Regular build
await VerifyError();
// Incremental build
await VerifyError();
async Task VerifyError()
{
var result = await DotnetMSBuild("Build");
Assert.BuildFailed(result);
// This needs to be relative path. Tracked by https://github.com/aspnet/Razor/issues/2187.
var filePath = Path.Combine(Project.DirectoryPath, "Views", "Shared", "NavMenu.razor");
var location = filePath + "(1,2)";
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// Absolute paths on OSX don't work well.
location = null;
}
Assert.BuildError(result, "RZ1006", location: location);
// Compilation failed without creating the views assembly
Assert.FileDoesNotExist(result, IntermediateOutputPath, "MvcWithComponents.dll");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "MvcWithComponents.Views.dll");
// File with error does not get written to disk.
Assert.FileDoesNotExist(result, IntermediateOutputPath, "RazorComponents", "Views", "Shared", "NavMenu.razor.g.cs");
}
}
[Fact]
[InitializeTestProject("MvcWithComponents")]
public async Task BuildComponents_RegeneratesComponentDefinition_WhenFilesChange()
{
// Act - 1
var tagHelperOutputCache = Path.Combine(IntermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs");
var generatedDefinitionFile = Path.Combine(RazorComponentIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs");
// Assert - 1
var result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
var outputFile = Path.Combine(OutputPath, "MvcWithComponents.dll");
Assert.FileExists(result, OutputPath, "MvcWithComponents.dll");
var outputAssemblyThumbprint = GetThumbPrint(outputFile);
Assert.FileExists(result, generatedDefinitionFile);
var generatedDefinitionThumbprint = GetThumbPrint(generatedDefinitionFile);
Assert.FileExists(result, generatedFile);
var generatedFileThumbprint = GetThumbPrint(generatedFile);
Assert.FileExists(result, tagHelperOutputCache);
Assert.FileContains(
result,
tagHelperOutputCache,
@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
var definitionThumbprint = GetThumbPrint(tagHelperOutputCache);
// Act - 2
ReplaceContent("Different things", "Views", "Shared", "NavMenu.razor");
result = await DotnetMSBuild("Build");
// Assert - 2
Assert.FileExists(result, OutputPath, "MvcWithComponents.dll");
Assert.NotEqual(outputAssemblyThumbprint, GetThumbPrint(outputFile));
Assert.FileExists(result, generatedDefinitionFile);
Assert.NotEqual(generatedDefinitionThumbprint, GetThumbPrint(generatedDefinitionFile));
Assert.FileExists(result, generatedFile);
Assert.NotEqual(generatedFileThumbprint, GetThumbPrint(generatedFile));
Assert.FileExists(result, tagHelperOutputCache);
Assert.FileContains(
result,
tagHelperOutputCache,
@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
// TODO:
Assert.Equal(definitionThumbprint, GetThumbPrint(tagHelperOutputCache));
}
[Fact]
[InitializeTestProject("MvcWithComponents")]
public async Task BuildComponents_DoesNotModifyFiles_IfFilesDoNotChange()
{
// Act - 1
var tagHelperOutputCache = Path.Combine(IntermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache");
var file = Path.Combine(Project.DirectoryPath, "Views", "Shared", "NavMenu.razor.g.cs");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs");
var generatedDefinitionFile = Path.Combine(RazorComponentIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs");
// Assert - 1
var result = await DotnetMSBuild("Build");
Assert.BuildPassed(result);
var outputFile = Path.Combine(OutputPath, "MvcWithComponents.dll");
Assert.FileExists(result, OutputPath, "MvcWithComponents.dll");
var outputAssemblyThumbprint = GetThumbPrint(outputFile);
Assert.FileExists(result, generatedDefinitionFile);
var generatedDefinitionThumbprint = GetThumbPrint(generatedDefinitionFile);
Assert.FileExists(result, generatedFile);
var generatedFileThumbprint = GetThumbPrint(generatedFile);
Assert.FileExists(result, tagHelperOutputCache);
Assert.FileContains(
result,
tagHelperOutputCache,
@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
var definitionThumbprint = GetThumbPrint(tagHelperOutputCache);
// Act - 2
result = await DotnetMSBuild("Build");
// Assert - 2
Assert.FileExists(result, OutputPath, "MvcWithComponents.dll");
Assert.Equal(outputAssemblyThumbprint, GetThumbPrint(outputFile));
Assert.FileExists(result, generatedDefinitionFile);
Assert.Equal(generatedDefinitionThumbprint, GetThumbPrint(generatedDefinitionFile));
Assert.FileExists(result, generatedFile);
Assert.Equal(generatedFileThumbprint, GetThumbPrint(generatedFile));
Assert.FileExists(result, tagHelperOutputCache);
Assert.FileContains(
result,
tagHelperOutputCache,
@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
Assert.Equal(definitionThumbprint, GetThumbPrint(tagHelperOutputCache));
}
[Fact]
[InitializeTestProject("AppWithP2PReference", additionalProjects: "ClassLibrary")]
public async Task IncrementalBuild_WithP2P_WorksWhenBuildProjectReferencesIsDisabled()

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
var result = await DotnetMSBuild("Build", "/t:_IntrospectRazorCompileItems");
Assert.BuildPassed(result);
Assert.BuildOutputContainsLine(result, $"RazorCompile: {Path.Combine(IntermediateOutputPath, "Razor", "Views", "Home", "Index.g.cshtml.cs")}");
Assert.BuildOutputContainsLine(result, $"RazorCompile: {Path.Combine(IntermediateOutputPath, "Razor", "Views", "Home", "Index.cshtml.g.cs")}");
Assert.BuildOutputContainsLine(result, $"RazorCompile: {Path.Combine(IntermediateOutputPath, "SimpleMvc.RazorTargetAssemblyInfo.cs")}");
}

View File

@ -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 System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public class BuildPerformanceTest : MSBuildIntegrationTestBase, IClassFixture<BuildServerTestFixture>
{
public BuildPerformanceTest(BuildServerTestFixture buildServer)
: base(buildServer)
{
}
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task BuildMvcApp()
{
var result = await DotnetMSBuild(target: default, args: "/clp:PerformanceSummary");
Assert.BuildPassed(result);
var summary = ParseTaskPerformanceSummary(result.Output);
Assert.Equal(1, summary.First(f => f.Name == "RazorGenerate").Calls);
Assert.Equal(1, summary.First(f => f.Name == "RazorTagHelper").Calls);
// Incremental builds
for (var i = 0; i < 2; i++)
{
result = await DotnetMSBuild(target: default, args: "/clp:PerformanceSummary");
Assert.BuildPassed(result);
summary = ParseTaskPerformanceSummary(result.Output);
Assert.DoesNotContain(summary, item => item.Name == "RazorGenerate");
Assert.DoesNotContain(summary, item => item.Name == "RazorTagHelper");
}
}
[Fact]
[InitializeTestProject("MvcWithComponents")]
public async Task BuildMvcAppWithComponents()
{
var result = await DotnetMSBuild(target: default, args: "/clp:PerformanceSummary");
Assert.BuildPassed(result);
var summary = ParseTaskPerformanceSummary(result.Output);
// One for declaration build, one for the "real" code gen
Assert.Equal(2, summary.First(f => f.Name == "RazorGenerate").Calls);
Assert.Equal(1, summary.First(f => f.Name == "RazorTagHelper").Calls);
// Incremental builds
for (var i = 0; i < 2; i++)
{
result = await DotnetMSBuild(target: default, args: "/clp:PerformanceSummary");
Assert.BuildPassed(result);
summary = ParseTaskPerformanceSummary(result.Output);
Assert.DoesNotContain(summary, item => item.Name == "RazorGenerate");
Assert.DoesNotContain(summary, item => item.Name == "RazorTagHelper");
}
}
private List<PerformanceSummaryEntry> ParseTaskPerformanceSummary(string output)
{
const string Header = "Task Performance Summary:";
var lines = output.Split(Environment.NewLine);
var taskSection = Array.LastIndexOf(lines, Header);
Assert.True(taskSection != -1, $"Could not find line ${Header} in {output}");
var entries = new List<PerformanceSummaryEntry>();
// 6 ms FindAppConfigFile 4 calls
var matcher = new Regex(@"\s+(?<time>\w+) ms\s+(?<name>\w+)\s+(?<calls>\d+) calls");
for (var i = taskSection + 1; i < lines.Length; i++)
{
if (string.IsNullOrWhiteSpace(lines[i]))
{
continue;
}
var match = matcher.Match(lines[i]);
Assert.True(match.Success, $"Line {lines[i]} did not match.");
var entry = new PerformanceSummaryEntry
{
Name = match.Groups["name"].Value,
Calls = int.Parse(match.Groups["calls"].Value),
};
entries.Add(entry);
}
return entries;
}
private class PerformanceSummaryEntry
{
public string Name { get; set; }
public int Calls { get; set; }
}
}
}

View File

@ -3,14 +3,10 @@
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Tools;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.CodeAnalysis;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
@ -92,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, IntermediateOutputPath, "Whitespace in name.Views.dll");
Assert.FileExists(result, IntermediateOutputPath, "Whitespace in name.RazorCoreGenerate.cache");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs");
}
[Fact]
@ -230,6 +226,35 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
[Fact]
[InitializeTestProject("MvcWithComponents")]
public async Task Build_MvcWithComponents()
{
var tagHelperOutputCacheFile = Path.Combine(IntermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache");
var result = await DotnetMSBuild(
"Build",
"/p:_RazorForceBuildServer=true");
Assert.BuildPassed(result);
Assert.FileExists(result, OutputPath, "MvcWithComponents.dll");
Assert.FileExists(result, OutputPath, "MvcWithComponents.pdb");
Assert.FileExists(result, OutputPath, "MvcWithComponents.Views.dll");
Assert.FileExists(result, OutputPath, "MvcWithComponents.Views.pdb");
// Verify tag helper discovery from components work
Assert.FileExists(result, tagHelperOutputCacheFile);
Assert.FileContains(
result,
tagHelperOutputCacheFile,
@"""Name"":""MvcWithComponents.TestComponent""");
Assert.FileContains(
result,
tagHelperOutputCacheFile,
@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
}
private class TestProjectDirectory : ProjectDirectory
{
public TestProjectDirectory(string solutionPath, string directoryPath, string projectFilePath)

View File

@ -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 System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public class BuildWithComponentsIntegrationTest : MSBuildIntegrationTestBase, IClassFixture<BuildServerTestFixture>
{
public BuildWithComponentsIntegrationTest(BuildServerTestFixture buildServer)
: base(buildServer)
{
}
[Fact]
[InitializeTestProject("MvcWithComponents")]
public Task Build_Components_WithDotNetCoreMSBuild_Works() => Build_ComponentsWorks(MSBuildProcessKind.Dotnet);
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
[InitializeTestProject("MvcWithComponents")]
public Task Build_Components_WithDesktopMSBuild_Works() => Build_ComponentsWorks(MSBuildProcessKind.Desktop);
private async Task Build_ComponentsWorks(MSBuildProcessKind msBuildProcessKind)
{
var result = await DotnetMSBuild("Build", msBuildProcessKind: msBuildProcessKind);
Assert.BuildPassed(result);
Assert.FileExists(result, OutputPath, "MvcWithComponents.dll");
Assert.FileExists(result, OutputPath, "MvcWithComponents.pdb");
Assert.FileExists(result, OutputPath, "MvcWithComponents.Views.dll");
Assert.FileExists(result, OutputPath, "MvcWithComponents.Views.pdb");
Assert.AssemblyContainsType(result, Path.Combine(OutputPath, "MvcWithComponents.dll"), "MvcWithComponents.TestComponent");
Assert.AssemblyContainsType(result, Path.Combine(OutputPath, "MvcWithComponents.dll"), "MvcWithComponents.Views.Shared.NavMenu");
}
}
}

View File

@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
Assert.BuildOutputContainsLine(
result,
$@"RazorGenerateWithTargetPath: {filePath} {filePath} {Path.Combine(RazorIntermediateOutputPath, Path.ChangeExtension(filePath, ".g.cshtml.cs"))}");
$@"RazorGenerateWithTargetPath: {filePath} {filePath} {Path.Combine(RazorIntermediateOutputPath, filePath + ".g.cs")}");
}
}
}

View File

@ -44,6 +44,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
protected string RazorIntermediateOutputPath => Path.Combine(IntermediateOutputPath, "Razor");
protected string RazorComponentIntermediateOutputPath => Path.Combine(IntermediateOutputPath, "RazorDeclaration");
internal static string TargetFramework
{
get => _projectTfm.Value;

View File

@ -38,15 +38,15 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Path.Combine(IntermediateOutputPath, "SimpleMvc.TagHelpers.output.cache"),
@"""Name"":""SimpleMvc.SimpleTagHelper""");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewImports.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewStart.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Contact.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_Layout.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_ValidationScriptsPartial.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "Error.g.cshtml.cs");
Assert.FileCountEquals(result, 8, RazorIntermediateOutputPath, "*.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewImports.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewStart.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Contact.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_Layout.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_ValidationScriptsPartial.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "Error.cshtml.g.cs");
Assert.FileCountEquals(result, 8, RazorIntermediateOutputPath, "*.cshtml.g.cs");
}
[Fact]
@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileDoesNotExist(result, IntermediateOutputPath, "SimpleMvc.Views.dll");
// If there's a parser error, the generated file contents is likely incorrect. The file should not be written to disk.
Assert.FileDoesNotExist(result, RazorIntermediateOutputPath, "Views", "Home", "Index.g.cshtml.cs");
Assert.FileDoesNotExist(result, RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs");
}
[Fact]
@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
// Act - 1
var result = await DotnetMSBuild(RazorGenerateTarget);
var generatedFile = Path.Combine(Project.DirectoryPath, RazorIntermediateOutputPath, "Views", "Home", "About.g.cshtml.cs");
var generatedFile = Path.Combine(Project.DirectoryPath, RazorIntermediateOutputPath, "Views", "Home", "About.cshtml.g.cs");
// Assert - 1
Assert.BuildPassed(result);
@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
// Act - 1
var result = await DotnetMSBuild(RazorGenerateTarget);
var file = Path.Combine(Project.DirectoryPath, "Views", "Home", "Contact.cshtml");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Contact.g.cshtml.cs");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Contact.cshtml.g.cs");
// Assert - 1
Assert.BuildPassed(result);
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
// Act - 1
var result = await DotnetMSBuild(RazorGenerateTarget);
var file = Path.Combine(Project.DirectoryPath, RazorIntermediateOutputPath, "Views", "Home", "About.g.cshtml.cs");
var file = Path.Combine(Project.DirectoryPath, RazorIntermediateOutputPath, "Views", "Home", "About.cshtml.g.cs");
// Assert - 1
Assert.BuildPassed(result);
@ -169,7 +169,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
var result = await DotnetMSBuild(RazorGenerateTarget);
var file = Path.Combine(Project.DirectoryPath, "Views", "Home", "Index.cshtml");
var renamed = Path.Combine(Project.DirectoryPath, "Views", "Home", "NewIndex.cshtml");
var generated = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.g.cshtml.cs");
var generated = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs");
// Assert - 1
Assert.BuildPassed(result);
@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
// Assert - 2
Assert.BuildPassed(result);
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "NewIndex.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "NewIndex.cshtml.g.cs");
Assert.FileDoesNotExist(result, generated);
}
@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
// Act - 1
var result = await DotnetMSBuild(RazorGenerateTarget);
var file = Path.Combine(Project.DirectoryPath, "Views", "Home", "Index.cshtml");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.g.cshtml.cs");
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs");
// Assert - 1
Assert.BuildPassed(result);
@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
// We shouldn't need to hash the files
Assert.FileDoesNotExist(result, Path.Combine(IntermediateOutputPath, "SimpleMvc.RazorCoreGenerate.cache"));
Assert.FileCountEquals(result, 0, RazorIntermediateOutputPath, "*.g.cshtml.cs");
Assert.FileCountEquals(result, 0, RazorIntermediateOutputPath, "*.cshtml.g.cs");
}
[Fact]
@ -243,8 +243,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.g.cshtml.cs");
Assert.FileCountEquals(result, 1, RazorIntermediateOutputPath, "*.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.cshtml.g.cs");
Assert.FileCountEquals(result, 1, RazorIntermediateOutputPath, "*.cshtml.g.cs");
}
[Fact]
@ -265,8 +265,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.g.cshtml.cs");
Assert.FileCountEquals(result, 1, RazorIntermediateOutputPath, "*.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.cshtml.g.cs");
Assert.FileCountEquals(result, 1, RazorIntermediateOutputPath, "*.cshtml.g.cs");
}
[Fact]
@ -287,13 +287,13 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
Assert.FileExists(result, RazorIntermediateOutputPath, "LinkedFile.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFile2.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFileWithRename.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "LinkedFile.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFile2.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFileWithRename.cshtml.g.cs");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {Path.Combine("..", "LinkedDir", "LinkedFile.cshtml")} LinkedFile.cshtml {Path.Combine(RazorIntermediateOutputPath, "LinkedFile.g.cshtml.cs")}");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {Path.Combine("..", "LinkedDir", "LinkedFile2.cshtml")} LinkedFileOut\LinkedFile2.cshtml {Path.Combine(RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFile2.g.cshtml.cs")}");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {Path.Combine("..", "LinkedDir", "LinkedFile3.cshtml")} LinkedFileOut\LinkedFileWithRename.cshtml {Path.Combine(RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFileWithRename.g.cshtml.cs")}");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {Path.Combine("..", "LinkedDir", "LinkedFile.cshtml")} LinkedFile.cshtml {Path.Combine(RazorIntermediateOutputPath, "LinkedFile.cshtml.g.cs")}");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {Path.Combine("..", "LinkedDir", "LinkedFile2.cshtml")} LinkedFileOut\LinkedFile2.cshtml {Path.Combine(RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFile2.cshtml.g.cs")}");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {Path.Combine("..", "LinkedDir", "LinkedFile3.cshtml")} LinkedFileOut\LinkedFileWithRename.cshtml {Path.Combine(RazorIntermediateOutputPath, "LinkedFileOut", "LinkedFileWithRename.cshtml.g.cs")}");
}
[Fact]
@ -342,17 +342,17 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.dll");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "SimpleMvc.Views.dll");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewImports.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewStart.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Contact.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_Layout.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_ValidationScriptsPartial.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "Error.g.cshtml.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "temp.g.cshtml.cs");
Assert.FileCountEquals(result, 9, RazorIntermediateOutputPath, "*.g.cshtml.cs");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {filePath} temp.cshtml {Path.Combine(RazorIntermediateOutputPath, "temp.g.cshtml.cs")}");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewImports.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "_ViewStart.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "About.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Contact.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_Layout.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "_ValidationScriptsPartial.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Shared", "Error.cshtml.g.cs");
Assert.FileExists(result, RazorIntermediateOutputPath, "temp.cshtml.g.cs");
Assert.FileCountEquals(result, 9, RazorIntermediateOutputPath, "*.cshtml.g.cs");
Assert.BuildOutputContainsLine(result, $@"RazorGenerateWithTargetPath: {filePath} temp.cshtml {Path.Combine(RazorIntermediateOutputPath, "temp.cshtml.g.cs")}");
}
}
}

View File

@ -23,7 +23,6 @@
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">

View File

@ -26,7 +26,6 @@
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">

View File

@ -25,7 +25,6 @@
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">

View File

@ -17,7 +17,6 @@
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">

View File

@ -0,0 +1,11 @@
using System;
namespace MvcWithComponents.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RazorSdkDirectoryRoot>$(RazorSdkProjectDirectory)bin\$(Configuration)\sdk-output\</RazorSdkDirectoryRoot>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(RunningAsTest)' == ''">
<!-- We don't want to run build server when not running as tests. -->
<UseRazorBuildServer>false</UseRazorBuildServer>
</PropertyGroup>
<ItemGroup Condition="'$(BinariesRoot)'==''">
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
</ItemGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Html.Abstractions.dll"/>
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.dll"/>
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Runtime.dll"/>
<Reference Include="$(BinariesRoot)\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.dll"/>
</ItemGroup>
<!-- Test Placeholder -->
</Project>

View File

@ -0,0 +1,13 @@

namespace MvcWithComponents
{
public class Program
{
public static void Main(string[] args)
{
// Just make sure we have a reference to the MvcShim
var t = typeof(Microsoft.AspNetCore.Mvc.IActionResult);
System.Console.WriteLine(t.FullName);
}
}
}

View File

@ -0,0 +1 @@
Hello from component

View File

@ -0,0 +1,5 @@
@{
ViewData["Title"] = "Home Page";
}
Hello world!

View File

@ -0,0 +1,2 @@
NavMenu content

View File

@ -0,0 +1,3 @@
@using MvcWithComponents
@using MvcWithComponents.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -22,7 +22,6 @@
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">

View File

@ -17,7 +17,6 @@
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
<ProjectReference Include="..\..\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup Condition="'$(BinariesRoot)'!=''">