Implement Components with new HTML parser

This change ports the existing Blazor component compiler features to
work on top of the Razor compiler. These new features are on once Razor
Language version is set to V3 or higher.

The tests and code for the old Blazor compiler were added to this repo
in previous changes. This change adds the necessary enabling features,
and gets the tests working.

Code generation for tests tests is identical with a few exceptions.
Design time directives now include line mappings (as they should). The
infrastructure seems to prefer windows style line-endings whereas the
Blazor code did the opposite. I plan to leave this alone unless it's a
problem for testing on *nix.

IR Generation will be cosmetically different due to different node names
and formatting. Structure should generally be the same.

----

In order to implement this support, I had to add the following core
features.

Configuring passes/engine-features based on the langauge version. This
required revisiting a *lot* of old tests and forcing them to use a
version appropriate Razor engine. A lot of our tests for legacy things
were using new versions of the language :(.

Configuring passes/engine-features to switch on/off depending on the
document kind.

Configuring directives depending on the file-kind. Directives are a
parser feature, so this decision needs to be made before the IR phase.
Directives that are specific to a file kind will override directives
that are not - this allows components to customize templates for
instance.

Adding functions/inherits/implements directive as mainline features that
are always on in V3. I'm not quite motivated to do this yet for
typeparam, but we might end up there. Remember that directives are
reserved words in Razor.
\n\nCommit migrated from a04addb86f
This commit is contained in:
Ryan Nowak 2018-12-21 12:35:48 -08:00
parent 45ad1c0ae8
commit 0c1c05efc6
115 changed files with 4089 additions and 1478 deletions

View File

@ -21,6 +21,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
throw new ArgumentNullException(nameof(projectItem));
}
// Don't add MVC imports for a component - this shouldn't happen for v1, but just in case.
if (string.Equals(projectItem.FileKind, FileKinds.Component, StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<RazorProjectItem>();
}
var imports = new List<RazorProjectItem>();
AddDefaultDirectivesImport(imports);

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
builder.Features.Add(new ModelExpressionPass());
builder.Features.Add(new MvcViewDocumentClassifierPass());
builder.SetImportFeature(new MvcImportProjectFeature());
builder.Features.Add(new MvcImportProjectFeature());
}
public static void RegisterViewComponentTagHelpers(RazorProjectEngineBuilder builder)

View File

@ -1,6 +1,7 @@
// 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.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -173,7 +174,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
private RazorEngine CreateEngine()
{
return RazorProjectEngine.Create(b =>
var configuration = RazorConfiguration.Create(RazorLanguageVersion.Version_1_1, "test", Array.Empty<RazorExtension>());
return RazorProjectEngine.Create(configuration, RazorProjectFileSystem.Empty, b =>
{
// Notice we're not registering the InjectDirective.Pass here so we can run it on demand.
b.AddDirective(InjectDirective.Directive);

View File

@ -11,8 +11,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
{
public class ModelDirectiveTest
public class ModelDirectiveTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_1_1;
[Fact]
public void ModelDirective_GetModelType_GetsTypeFromFirstWellFormedDirective()
{
@ -23,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
@model
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
@ -40,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
// Arrange
var codeDocument = CreateDocument(@" ");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
@ -60,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
@model Type1
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -87,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
@model Type2
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -113,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
@model Type1
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -138,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
@inherits BaseType<TModel>
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -234,7 +236,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
return visitor.Node;
}
private RazorEngine CreateEngine()
private RazorEngine CreateRuntimeEngine()
{
return CreateEngineCore();
}
@ -246,7 +248,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
private RazorEngine CreateEngineCore(bool designTime = false)
{
return RazorProjectEngine.Create(b =>
return CreateProjectEngine(b =>
{
// Notice we're not registering the ModelDirective.Pass here so we can run it on demand.
b.AddDirective(ModelDirective.Directive);

View File

@ -8,8 +8,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
{
public class MvcRazorTemplateEngineTest
public class MvcRazorTemplateEngineTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_1_1;
[Fact]
public void GetDefaultImports_IncludesDefaultImports()
{
@ -25,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
"@using Microsoft.AspNetCore.Mvc.ViewFeatures",
};
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
RazorProjectEngine.Create().Engine,
CreateProjectEngine().Engine,
new TestRazorProjectFileSystem());
// Act
@ -51,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
"@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider",
};
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
RazorProjectEngine.Create().Engine,
CreateProjectEngine().Engine,
new TestRazorProjectFileSystem());
// Act
@ -69,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
{
// Arrange
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
RazorProjectEngine.Create().Engine,
CreateProjectEngine().Engine,
new TestRazorProjectFileSystem());
// Act

View File

@ -7,8 +7,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
{
public class MvcViewDocumentClassifierPassTest
public class MvcViewDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_1_1;
[Fact]
public void MvcViewDocumentClassifierPass_SetsDocumentKind()
{
@ -217,8 +219,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
Assert.Equal(new[] { "public", "async", "override" }, visitor.Method.Modifiers);
}
private static RazorProjectEngine CreateProjectEngine() => RazorProjectEngine.Create();
private static DocumentIntermediateNode CreateIRDocument(RazorProjectEngine engine, RazorCodeDocument codeDocument)
{
for (var i = 0; i < engine.Phases.Count; i++)

View File

@ -21,6 +21,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
throw new ArgumentNullException(nameof(projectItem));
}
// Don't add MVC imports for a component - this shouldn't happen for v2, but just in case.
if (string.Equals(projectItem.FileKind, FileKinds.Component, StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<RazorProjectItem>();
}
var imports = new List<RazorProjectItem>();
AddDefaultDirectivesImport(imports);

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
throw new ArgumentNullException(nameof(builder));
}
FunctionsDirective.Register(builder);
InjectDirective.Register(builder);
ModelDirective.Register(builder);
NamespaceDirective.Register(builder);
@ -42,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
builder.Features.Add(new AssemblyAttributeInjectionPass());
builder.Features.Add(new InstrumentationPass());
builder.SetImportFeature(new MvcImportProjectFeature());
builder.Features.Add(new MvcImportProjectFeature());
}
}
}

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
public static readonly string RouteTemplateKey = "RouteTemplate";
private static readonly RazorProjectEngine LeadingDirectiveParsingEngine = RazorProjectEngine.Create(
RazorConfiguration.Default,
RazorConfiguration.Create(RazorLanguageVersion.Version_2_1, "leading-directive-parser", Array.Empty<RazorExtension>()),
RazorProjectFileSystem.Create("/"),
builder =>
{

View File

@ -7,8 +7,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
{
public class AssemblyAttributeInjectionPassTest
public class AssemblyAttributeInjectionPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_2_1;
[Fact]
public void Execute_NoOps_IfNamespaceNodeIsMissing()
{
@ -20,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
// Act
@ -45,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
// Act
@ -71,7 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
// Act
@ -112,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
// Act
@ -147,7 +149,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
// Act
@ -190,7 +192,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
var source = TestRazorSourceDocument.Create("test", new RazorSourceDocumentProperties(filePath: null, relativePath: "/Views/Index.cshtml"));
@ -237,7 +239,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
var source = TestRazorSourceDocument.Create("test", new RazorSourceDocumentProperties(filePath: null, relativePath: "/Views/Index.cshtml"));
@ -290,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
var source = TestRazorSourceDocument.Create("test", new RazorSourceDocumentProperties(filePath: null, relativePath: "\\test\\\"Index.cshtml"));
@ -349,7 +351,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
var source = TestRazorSourceDocument.Create("test", new RazorSourceDocumentProperties(filePath: null, relativePath: "/Views/Index.cshtml"));
@ -404,7 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var pass = new AssemblyAttributeInjectionPass
{
Engine = RazorProjectEngine.Create().Engine,
Engine = CreateProjectEngine().Engine,
};
var source = TestRazorSourceDocument.Create("test", new RazorSourceDocumentProperties(filePath: null, relativePath: "test\\\"Index.cshtml"));

View File

@ -6,12 +6,28 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.IntegrationTests;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IntegrationTests
{
public class InstrumentationPassIntegrationTest : IntegrationTestBase
{
private readonly static CSharpCompilation DefaultBaseCompilation = MvcShim.BaseCompilation.WithAssemblyName("AppCode");
public InstrumentationPassIntegrationTest()
: base(generateBaselines: null)
{
Configuration = RazorConfiguration.Create(
RazorLanguageVersion.Version_2_0,
"MVC-2.1",
new[] { new AssemblyExtension("MVC-2.1", typeof(ExtensionInitializer).Assembly) });
}
protected override CSharpCompilation BaseCompilation => DefaultBaseCompilation;
protected override RazorConfiguration Configuration { get; }
[Fact]
public void BasicTest()
{

View File

@ -1,8 +1,6 @@
// 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;
using Microsoft.AspNetCore.Razor.Language.Extensions;
@ -11,8 +9,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
{
public class ModelDirectiveTest
public class ModelDirectiveTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_2_1;
[Fact]
public void ModelDirective_GetModelType_GetsTypeFromFirstWellFormedDirective()
{
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
@model
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
// Arrange
var codeDocument = CreateDocument(@" ");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
@model Type1
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
@model Type2
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
@model Type1
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
@inherits BaseType<TModel>
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -234,7 +234,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
return visitor.Node;
}
private RazorEngine CreateEngine()
private RazorEngine CreateRuntimeEngine()
{
return CreateEngineCore();
}
@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
private RazorEngine CreateEngineCore(bool designTime = false)
{
return RazorProjectEngine.Create(b =>
return CreateProjectEngine(b =>
{
// Notice we're not registering the ModelDirective.Pass here so we can run it on demand.
b.AddDirective(ModelDirective.Directive);

View File

@ -7,8 +7,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
{
public class MvcViewDocumentClassifierPassTest
public class MvcViewDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_2_1;
[Fact]
public void MvcViewDocumentClassifierPass_SetsDocumentKind()
{
@ -217,8 +219,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
Assert.Equal(new[] { "public", "async", "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++)

View File

@ -9,8 +9,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
{
public class RazorPageDocumentClassifierPassTest
public class RazorPageDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_2_1;
[Fact]
public void RazorPageDocumentClassifierPass_LogsErrorForImportedPageDirectives()
{
@ -20,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var importDocument = RazorSourceDocument.Create("@page", "import.cshtml");
var sourceDocument = RazorSourceDocument.Create("<p>Hello World</p>", "main.cshtml");
var codeDocument = RazorCodeDocument.Create(sourceDocument, new[] { importDocument });
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -55,7 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
";
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(content, "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -85,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
";
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(content, "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -109,7 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -129,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
irDocument.DocumentKind = "some-value";
var pass = new RazorPageDocumentClassifierPass
@ -150,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page+1", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
irDocument.DocumentKind = "some-value";
var pass = new RazorPageDocumentClassifierPass
@ -171,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -194,7 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: "Test.cshtml");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -219,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var properties = new RazorSourceDocumentProperties(filePath: null, relativePath: null);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -246,7 +248,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: relativePath);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -269,7 +271,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var properties = new RazorSourceDocumentProperties(filePath: @"x::\application\Views\Home\Index.cshtml", relativePath: null);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -292,7 +294,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var properties = new RazorSourceDocumentProperties(filePath: @"x:\Test.cshtml", relativePath: "path.with+invalid-chars");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -314,7 +316,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -339,7 +341,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: "Test.cshtml");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page \"some-route\"", properties));
var engine = CreateEngine();
var engine = CreateProjectEngine().Engine;
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -357,14 +359,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
Assert.Equal("some-route", attributeNode.Value);
}
private static RazorEngine CreateEngine()
protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
{
return RazorProjectEngine.Create(b =>
{
PageDirective.Register(b);
}).Engine;
PageDirective.Register(builder);
}
private static DocumentIntermediateNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument)
{
for (var i = 0; i < engine.Phases.Count; i++)

View File

@ -1,12 +1,20 @@
#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
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCore.TestFiles_IntegrationTests_InstrumentationPassIntegrationTest_BasicTest), @"mvc.1.0.view", @"/TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml")]
[assembly:global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(@"/TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml", typeof(AspNetCore.TestFiles_IntegrationTests_InstrumentationPassIntegrationTest_BasicTest))]
namespace AspNetCore
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"78993008d95836bec2b9175d4294bf7bd5f5f109", @"/TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml")]
public class Template
public class TestFiles_IntegrationTests_InstrumentationPassIntegrationTest_BasicTest : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
{
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);
@ -34,9 +42,12 @@ namespace Razor
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
BeginContext(31, 28, true);
BeginContext(31, 28, true);
WriteLiteral("<span someattr>Hola</span>\r\n");
EndContext();
EndContext();
BeginContext(61, 7, false);
BeginContext(61, 7, false);
#line 3 "TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml"
Write("Hello");
@ -44,14 +55,21 @@ Write("Hello");
#line default
#line hidden
EndContext();
EndContext();
BeginContext(69, 2, true);
BeginContext(69, 2, true);
WriteLiteral("\r\n");
EndContext();
EndContext();
BeginContext(71, 87, false);
BeginContext(71, 87, false);
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("form", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
BeginContext(91, 6, true);
BeginContext(91, 6, true);
WriteLiteral("\r\n ");
EndContext();
EndContext();
BeginContext(97, 52, false);
BeginContext(97, 52, false);
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, "test", async() => {
}
@ -75,9 +93,12 @@ __InputTagHelper.BarProp = DateTime.Now;
Write(__tagHelperExecutionContext.Output);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
EndContext();
EndContext();
BeginContext(149, 2, true);
BeginContext(149, 2, true);
WriteLiteral("\r\n");
EndContext();
EndContext();
}
);
__FormTagHelper = CreateTagHelper<global::FormTagHelper>();
@ -91,19 +112,27 @@ __InputTagHelper.BarProp = DateTime.Now;
Write(__tagHelperExecutionContext.Output);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
EndContext();
EndContext();
BeginContext(158, 31, true);
BeginContext(158, 31, true);
WriteLiteral("\r\n\r\n<span>Here is some content ");
EndContext();
EndContext();
BeginContext(207, 9, true);
BeginContext(207, 9, true);
WriteLiteral("</span>\r\n");
EndContext();
EndContext();
BeginContext(217, 29, false);
BeginContext(217, 29, false);
#line 9 "TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml"
Write(Foo(item => new Template(async(__razor_template_writer) => {
Write(Foo(item => new global::Microsoft.AspNetCore.Mvc.Razor.HelperResult(async(__razor_template_writer) => {
PushWriter(__razor_template_writer);
BeginContext(222, 24, true);
BeginContext(222, 24, true);
WriteLiteral("<span>Hello world</span>");
EndContext();
EndContext();
PopWriter();
}
)));
@ -111,8 +140,19 @@ Write(Foo(item => new Template(async(__razor_template_writer) => {
#line default
#line hidden
EndContext();
EndContext();
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
}
}
#pragma warning restore 1591

View File

@ -1,8 +1,17 @@
Document -
RazorCompiledItemAttribute -
NamespaceDeclaration - - Razor
CSharpCode -
IntermediateToken - - CSharp - [assembly:global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(@"/TestFiles/IntegrationTests/InstrumentationPassIntegrationTest/BasicTest.cshtml", typeof(AspNetCore.TestFiles_IntegrationTests_InstrumentationPassIntegrationTest_BasicTest))]
NamespaceDeclaration - - AspNetCore
UsingDirective - (1:0,1 [14] ) - System
UsingDirective - (16:1,1 [34] ) - System.Collections.Generic
UsingDirective - (51:2,1 [19] ) - System.Linq
UsingDirective - (71:3,1 [30] ) - System.Threading.Tasks
UsingDirective - (102:4,1 [32] ) - Microsoft.AspNetCore.Mvc
UsingDirective - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingDirective - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
RazorSourceChecksumAttribute -
ClassDeclaration - - public - Template - -
ClassDeclaration - - public - TestFiles_IntegrationTests_InstrumentationPassIntegrationTest_BasicTest - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
PreallocatedTagHelperPropertyValue - - __tagHelperAttribute_0 - value - Hello - HtmlAttributeValueStyle.DoubleQuotes
PreallocatedTagHelperHtmlAttributeValue - - __tagHelperAttribute_1 - type - text - HtmlAttributeValueStyle.SingleQuotes
PreallocatedTagHelperHtmlAttributeValue - - __tagHelperAttribute_2 - unbound - foo - HtmlAttributeValueStyle.DoubleQuotes
@ -10,6 +19,8 @@ Document -
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);
CSharpCode -
IntermediateToken - - CSharp - BeginContext(31, 28, true);
HtmlContent - (31:1,0 [28] BasicTest.cshtml)
@ -21,28 +32,46 @@ Document -
IntermediateToken - (57:1,26 [2] BasicTest.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(61, 7, false);
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 - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(69, 2, true);
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 - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(71, 87, false);
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);
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 - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(97, 52, false);
CSharpCode -
IntermediateToken - - CSharp - BeginContext(97, 52, false);
TagHelper - (97:4,4 [52] BasicTest.cshtml) - input - TagMode.SelfClosing
@ -56,17 +85,27 @@ Document -
DefaultTagHelperExecute -
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(149, 2, true);
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();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
DefaultTagHelperCreate - - FormTagHelper
PreallocatedTagHelperHtmlAttribute - - __tagHelperAttribute_2
DefaultTagHelperExecute -
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(158, 31, true);
CSharpCode -
IntermediateToken - - CSharp - BeginContext(158, 31, true);
HtmlContent - (158:5,7 [31] BasicTest.cshtml)
@ -76,6 +115,10 @@ Document -
IntermediateToken - (168:7,6 [21] BasicTest.cshtml) - Html - Here is some content
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(207, 9, true);
CSharpCode -
IntermediateToken - - CSharp - BeginContext(207, 9, true);
HtmlContent - (207:7,45 [9] BasicTest.cshtml)
@ -83,11 +126,17 @@ Document -
IntermediateToken - (214:7,52 [2] BasicTest.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - BeginContext(217, 29, false);
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);
CSharpCode -
IntermediateToken - - CSharp - BeginContext(222, 24, true);
HtmlContent - (222:8,6 [24] BasicTest.cshtml)
@ -97,6 +146,15 @@ Document -
IntermediateToken - (239:8,23 [7] BasicTest.cshtml) - Html - </span>
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
IntermediateToken - (246:8,30 [1] BasicTest.cshtml) - CSharp - )
CSharpCode -
IntermediateToken - - CSharp - EndContext();
CSharpCode -
IntermediateToken - - CSharp - EndContext();
Inject -
Inject -
Inject -
Inject -
Inject -

View File

@ -21,6 +21,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
throw new ArgumentNullException(nameof(projectItem));
}
// Don't add MVC imports for a component
if (string.Equals(projectItem.FileKind, FileKinds.Component, StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<RazorProjectItem>();
}
var imports = new List<RazorProjectItem>();
AddDefaultDirectivesImport(imports);

View File

@ -21,8 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
ModelDirective.Register(builder);
NamespaceDirective.Register(builder);
PageDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
builder.Features.Add(new DefaultTagHelperDescriptorProvider());
@ -40,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
builder.Features.Add(new RazorPageDocumentClassifierPass());
builder.Features.Add(new MvcViewDocumentClassifierPass());
builder.SetImportFeature(new MvcImportProjectFeature());
builder.Features.Add(new MvcImportProjectFeature());
}
}
}

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
public static readonly string RouteTemplateKey = "RouteTemplate";
private static readonly RazorProjectEngine LeadingDirectiveParsingEngine = RazorProjectEngine.Create(
RazorConfiguration.Default,
RazorConfiguration.Create(RazorLanguageVersion.Version_3_0, "leading-directive-parser", Array.Empty<RazorExtension>()),
RazorProjectFileSystem.Create("/"),
builder =>
{

View File

@ -1,15 +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 System.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class InjectDirectiveTest
public class InjectDirectiveTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_3_0;
[Fact]
public void InjectDirectivePass_Execute_DefinesProperty()
{
@ -171,14 +172,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
return visitor.Node;
}
private RazorEngine CreateEngine()
protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
{
return RazorProjectEngine.Create(b =>
{
// Notice we're not registering the InjectDirective.Pass here so we can run it on demand.
b.AddDirective(InjectDirective.Directive);
b.AddDirective(ModelDirective.Directive);
}).Engine;
// Notice we're not registering the InjectDirective.Pass here so we can run it on demand.
builder.AddDirective(InjectDirective.Directive);
builder.AddDirective(ModelDirective.Directive);
}
private DocumentIntermediateNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument)

View File

@ -181,6 +181,20 @@ public class MyModel
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
}
[Fact]
public void BasicComponent_Runtime()
{
// Arrange
var projectItem = CreateProjectItemFromFile(fileKind: FileKinds.Component);
// Act
var compiled = CompileToAssembly(projectItem, designTime: false);
// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
}
[Fact]
public void Sections_Runtime()
{
@ -631,6 +645,21 @@ public class MyModel
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);
}
[Fact]
public void BasicComponent_DesignTime()
{
// Arrange
var projectItem = CreateProjectItemFromFile(fileKind: FileKinds.Component);
// Act
var compiled = CompileToAssembly(projectItem, designTime: true);
// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);
}
[Fact]
public void Sections_DesignTime()
{

View File

@ -1,8 +1,6 @@
// 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;
using Microsoft.AspNetCore.Razor.Language.Extensions;
@ -11,8 +9,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class ModelDirectiveTest
public class ModelDirectiveTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_3_0;
[Fact]
public void ModelDirective_GetModelType_GetsTypeFromFirstWellFormedDirective()
{
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
@model
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
// Arrange
var codeDocument = CreateDocument(@" ");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
@model Type1
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
@model Type2
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
@model Type1
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
@inherits BaseType<TModel>
");
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
@ -234,9 +234,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
return visitor.Node;
}
private RazorEngine CreateEngine()
private RazorEngine CreateRuntimeEngine()
{
return CreateEngineCore();
return CreateEngineCore(designTime: false);
}
private RazorEngine CreateDesignTimeEngine()
@ -246,14 +246,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
private RazorEngine CreateEngineCore(bool designTime = false)
{
return RazorProjectEngine.Create(b =>
return CreateProjectEngine(b =>
{
// Notice we're not registering the ModelDirective.Pass here so we can run it on demand.
b.AddDirective(ModelDirective.Directive);
// There's some special interaction with the inherits directive
InheritsDirective.Register(b);
b.Features.Add(new DesignTimeOptionsFeature(designTime));
}).Engine;
}

View File

@ -8,8 +8,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class MvcRazorTemplateEngineTest
public class MvcRazorTemplateEngineTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_3_0;
[Fact]
public void GetDefaultImports_IncludesDefaultImports()
{
@ -24,8 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
"@using Microsoft.AspNetCore.Mvc.Rendering",
"@using Microsoft.AspNetCore.Mvc.ViewFeatures",
};
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
RazorProjectEngine.Create().Engine,
CreateProjectEngine().Engine,
new TestRazorProjectFileSystem());
// Act
@ -51,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
"@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider",
};
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
RazorProjectEngine.Create().Engine,
CreateProjectEngine().Engine,
new TestRazorProjectFileSystem());
// Act
@ -69,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
// Arrange
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
RazorProjectEngine.Create().Engine,
CreateProjectEngine().Engine,
new TestRazorProjectFileSystem());
// Act

View File

@ -7,8 +7,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class MvcViewDocumentClassifierPassTest
public class MvcViewDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_3_0;
[Fact]
public void MvcViewDocumentClassifierPass_SetsDocumentKind()
{
@ -217,8 +219,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
Assert.Equal(new[] { "public", "async", "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++)

View File

@ -9,8 +9,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class RazorPageDocumentClassifierPassTest
public class RazorPageDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_3_0;
[Fact]
public void RazorPageDocumentClassifierPass_LogsErrorForImportedPageDirectives()
{
@ -20,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var importDocument = RazorSourceDocument.Create("@page", "import.cshtml");
var sourceDocument = RazorSourceDocument.Create("<p>Hello World</p>", "main.cshtml");
var codeDocument = RazorCodeDocument.Create(sourceDocument, new[] { importDocument });
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -55,7 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
";
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(content, "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -85,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
";
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(content, "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -109,7 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -129,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
irDocument.DocumentKind = "some-value";
var pass = new RazorPageDocumentClassifierPass
@ -150,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page+1", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
irDocument.DocumentKind = "some-value";
var pass = new RazorPageDocumentClassifierPass
@ -171,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -194,7 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: "Test.cshtml");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -219,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var properties = new RazorSourceDocumentProperties(filePath: null, relativePath: null);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -246,7 +248,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: relativePath);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -269,7 +271,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var properties = new RazorSourceDocumentProperties(filePath: @"x::\application\Views\Home\Index.cshtml", relativePath: null);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -292,7 +294,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var properties = new RazorSourceDocumentProperties(filePath: @"x:\Test.cshtml", relativePath: "path.with+invalid-chars");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", properties));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -314,7 +316,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page", "Test.cshtml"));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -339,7 +341,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var properties = new RazorSourceDocumentProperties(filePath: "ignored", relativePath: "Test.cshtml");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("@page \"some-route\"", properties));
var engine = CreateEngine();
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
var pass = new RazorPageDocumentClassifierPass
{
@ -357,9 +359,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
Assert.Equal("some-route", attributeNode.Value);
}
private static RazorEngine CreateEngine()
private RazorEngine CreateRuntimeEngine()
{
return RazorProjectEngine.Create(b =>
return CreateProjectEngine(b =>
{
PageDirective.Register(b);
}).Engine;

View File

@ -0,0 +1,9 @@
@implements IDisposable
<div class="@this.ToString()">
Hello world
@string.Format("{0}", "Hello")
</div>
@functions {
void IDisposable.Dispose(){ }
}

View File

@ -0,0 +1,53 @@
// <auto-generated/>
#pragma warning disable 1591
namespace AspNetCore
{
#line hidden
using TModel = global::System.Object;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public class BasicComponent : Microsoft.AspNetCore.Components.ComponentBase, IDisposable
{
#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
((System.Action)(() => {
#line 1 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml"
IDisposable __typeHelper = default(IDisposable);
#line default
#line hidden
}
))();
}
#pragma warning restore 219
#pragma warning disable 0414
private static System.Object __o = null;
#pragma warning restore 0414
#pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
#line 2 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml"
__o = this.ToString();
#line default
#line hidden
#line 4 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml"
__o = string.Format("{0}", "Hello");
#line default
#line hidden
}
#pragma warning restore 1998
#line 7 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml"
void IDisposable.Dispose(){ }
#line default
#line hidden
}
}
#pragma warning restore 1591

View File

@ -0,0 +1,37 @@
Document -
NamespaceDeclaration - - AspNetCore
UsingDirective - - TModel = global::System.Object
UsingDirective - (1:0,1 [12] ) - System
UsingDirective - (16:1,1 [32] ) - System.Collections.Generic
UsingDirective - (51:2,1 [17] ) - System.Linq
UsingDirective - (71:3,1 [28] ) - System.Threading.Tasks
UsingDirective - (102:4,1 [37] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - BasicComponent - Microsoft.AspNetCore.Components.ComponentBase - IDisposable
DesignTimeDirective -
DirectiveToken - (14:0,14 [36] ) - "*, Microsoft.AspNetCore.Components"
DirectiveToken - (12:0,12 [11] BasicComponent.cshtml) - IDisposable
CSharpCode -
IntermediateToken - - CSharp - #pragma warning disable 0414
CSharpCode -
IntermediateToken - - CSharp - private static System.Object __o = null;
CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree
CSharpCode -
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
MarkupElement - (25:1,0 [91] BasicComponent.cshtml) - div
HtmlAttribute - (29:1,4 [25] BasicComponent.cshtml) - class=" - "
CSharpExpressionAttributeValue - (37:1,12 [16] BasicComponent.cshtml) -
IntermediateToken - (38:1,13 [15] BasicComponent.cshtml) - CSharp - this.ToString()
HtmlContent - (55:1,30 [23] BasicComponent.cshtml)
IntermediateToken - (55:1,30 [23] BasicComponent.cshtml) - Html - \n Hello world\n
CSharpExpression - (79:3,5 [29] BasicComponent.cshtml)
IntermediateToken - (79:3,5 [29] BasicComponent.cshtml) - CSharp - string.Format("{0}", "Hello")
HtmlContent - (108:3,34 [2] BasicComponent.cshtml)
IntermediateToken - (108:3,34 [2] BasicComponent.cshtml) - Html - \n
HtmlContent - (116:4,6 [4] BasicComponent.cshtml)
IntermediateToken - (116:4,6 [4] BasicComponent.cshtml) - Html - \n\n
HtmlContent - (170:8,1 [2] BasicComponent.cshtml)
IntermediateToken - (170:8,1 [2] BasicComponent.cshtml) - Html - \n
CSharpCode - (132:6,12 [37] BasicComponent.cshtml)
IntermediateToken - (132:6,12 [37] BasicComponent.cshtml) - CSharp - \n void IDisposable.Dispose(){ }\n

View File

@ -0,0 +1,24 @@
Source Location: (12:0,12 [11] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|IDisposable|
Generated Location: (618:17,0 [11] )
|IDisposable|
Source Location: (38:1,13 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|this.ToString()|
Generated Location: (1214:33,13 [15] )
|this.ToString()|
Source Location: (79:3,5 [29] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|string.Format("{0}", "Hello")|
Generated Location: (1359:38,6 [29] )
|string.Format("{0}", "Hello")|
Source Location: (132:6,12 [37] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|
void IDisposable.Dispose(){ }
|
Generated Location: (1573:45,12 [37] )
|
void IDisposable.Dispose(){ }
|

View File

@ -0,0 +1,34 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d3c3d6059615673cb46fc4974164d61eabadb890"
// <auto-generated/>
#pragma warning disable 1591
namespace AspNetCore
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public class BasicComponent : Microsoft.AspNetCore.Components.ComponentBase, IDisposable
{
#pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", this.ToString());
builder.AddContent(2, "\r\n Hello world\r\n ");
builder.AddContent(3, string.Format("{0}", "Hello"));
builder.AddContent(4, "\r\n");
builder.CloseElement();
}
#pragma warning restore 1998
#line 7 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml"
void IDisposable.Dispose(){ }
#line default
#line hidden
}
}
#pragma warning restore 1591

View File

@ -0,0 +1,24 @@
Document -
NamespaceDeclaration - - AspNetCore
UsingDirective - (1:0,1 [14] ) - System
UsingDirective - (16:1,1 [34] ) - System.Collections.Generic
UsingDirective - (51:2,1 [19] ) - System.Linq
UsingDirective - (71:3,1 [30] ) - System.Threading.Tasks
UsingDirective - (102:4,1 [37] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - BasicComponent - Microsoft.AspNetCore.Components.ComponentBase - IDisposable
MethodDeclaration - - protected override - void - BuildRenderTree
CSharpCode -
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
MarkupElement - (25:1,0 [91] BasicComponent.cshtml) - div
HtmlAttribute - (29:1,4 [25] BasicComponent.cshtml) - class=" - "
CSharpExpressionAttributeValue - (37:1,12 [16] BasicComponent.cshtml) -
IntermediateToken - (38:1,13 [15] BasicComponent.cshtml) - CSharp - this.ToString()
HtmlContent - (55:1,30 [23] BasicComponent.cshtml)
IntermediateToken - (55:1,30 [19] BasicComponent.cshtml) - Html - \n Hello world\n
IntermediateToken - (74:3,0 [4] BasicComponent.cshtml) - Html -
CSharpExpression - (79:3,5 [29] BasicComponent.cshtml)
IntermediateToken - (79:3,5 [29] BasicComponent.cshtml) - CSharp - string.Format("{0}", "Hello")
HtmlContent - (108:3,34 [2] BasicComponent.cshtml)
IntermediateToken - (108:3,34 [2] BasicComponent.cshtml) - Html - \n
CSharpCode - (132:6,12 [37] BasicComponent.cshtml)
IntermediateToken - (132:6,12 [37] BasicComponent.cshtml) - CSharp - \n void IDisposable.Dispose(){ }\n

View File

@ -257,6 +257,46 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
VisitDefault(node);
}
public override void VisitComponent(ComponentIntermediateNode node)
{
Context.NodeWriter.WriteComponent(Context, node);
}
public override void VisitComponentAttribute(ComponentAttributeIntermediateNode node)
{
Context.NodeWriter.WriteComponentAttribute(Context, node);
}
public override void VisitComponentChildContent(ComponentChildContentIntermediateNode node)
{
Context.NodeWriter.WriteComponentChildContent(Context, node);
}
public override void VisitComponentTypeArgument(ComponentTypeArgumentIntermediateNode node)
{
Context.NodeWriter.WriteComponentTypeArgument(Context, node);
}
public override void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node)
{
Context.NodeWriter.WriteComponentTypeInferenceMethod(Context, node);
}
public override void VisitMarkupElement(MarkupElementIntermediateNode node)
{
Context.NodeWriter.WriteMarkupElement(Context, node);
}
public override void VisitMarkupBlock(MarkupBlockIntermediateNode node)
{
Context.NodeWriter.WriteMarkupBlock(Context, node);
}
public override void VisitReferenceCapture(ReferenceCaptureIntermediateNode node)
{
Context.NodeWriter.WriteReferenceCapture(Context, node);
}
public override void VisitDefault(IntermediateNode node)
{
Context.RenderChildren(node);

View File

@ -1,6 +1,7 @@
// 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.CodeGeneration
@ -23,6 +24,46 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public abstract void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node);
public virtual void WriteComponent(CodeRenderingContext context, ComponentIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public virtual void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public virtual void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public virtual void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public virtual void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public virtual void WriteMarkupElement(CodeRenderingContext context, MarkupElementIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public virtual void WriteMarkupBlock(CodeRenderingContext context, MarkupBlockIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public virtual void WriteReferenceCapture(CodeRenderingContext context, ReferenceCaptureIntermediateNode node)
{
throw new NotSupportedException("This writer does not support components.");
}
public abstract void BeginWriterScope(CodeRenderingContext context, string writer);
public abstract void EndWriterScope(CodeRenderingContext context);

View File

@ -1,42 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class BlazorRazorCSharpLoweringPhase : RazorEnginePhaseBase, IRazorCSharpLoweringPhase
{
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var documentNode = codeDocument.GetDocumentIntermediateNode();
ThrowForMissingDocumentDependency(documentNode);
#pragma warning disable CS0618
var writer = new DocumentWriterWorkaround().Create(documentNode.Target, documentNode.Options);
#pragma warning restore CS0618
try
{
var cSharpDocument = writer.WriteDocument(codeDocument, documentNode);
codeDocument.SetCSharpDocument(cSharpDocument);
}
catch (RazorCompilerException ex)
{
// Currently the Blazor code generation has some 'fatal errors' that can cause code generation
// to fail completely. This class is here to make that implementation work gracefully.
var cSharpDocument = RazorCSharpDocument.Create("", documentNode.Options, new[] { ex.Diagnostic });
codeDocument.SetCSharpDocument(cSharpDocument);
}
}
private class DocumentWriterWorkaround : DocumentWriter
{
public override RazorCSharpDocument WriteDocument(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -1,130 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
/// <summary>
/// Initializes the Blazor extension.
/// </summary>
public class BlazorExtensionInitializer : RazorExtensionInitializer
{
/// <summary>
/// Specifies the declaration configuration.
/// </summary>
public static readonly RazorConfiguration DeclarationConfiguration;
/// <summary>
/// Specifies the default configuration.
/// </summary>
public static readonly RazorConfiguration DefaultConfiguration;
static BlazorExtensionInitializer()
{
// The configuration names here need to match what we put in the MSBuild configuration
DeclarationConfiguration = RazorConfiguration.Create(
RazorLanguageVersion.Experimental,
"BlazorDeclaration-0.1",
Array.Empty<RazorExtension>());
DefaultConfiguration = RazorConfiguration.Create(
RazorLanguageVersion.Experimental,
"Blazor-0.1",
Array.Empty<RazorExtension>());
}
/// <summary>
/// Registers the Blazor extension.
/// </summary>
/// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
public static void Register(RazorProjectEngineBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
FunctionsDirective.Register(builder);
ImplementsDirective.Register(builder);
InheritsDirective.Register(builder);
InjectDirective.Register(builder);
LayoutDirective.Register(builder);
PageDirective.Register(builder);
TypeParamDirective.Register(builder);
builder.Features.Remove(builder.Features.OfType<IImportProjectFeature>().Single());
builder.Features.Add(new BlazorImportProjectFeature());
var index = builder.Phases.IndexOf(builder.Phases.OfType<IRazorCSharpLoweringPhase>().Single());
builder.Phases[index] = new BlazorRazorCSharpLoweringPhase();
builder.Features.Add(new ConfigureBlazorCodeGenerationOptions());
builder.AddTargetExtension(new BlazorTemplateTargetExtension());
var isDeclarationOnlyCompile = builder.Configuration.ConfigurationName == DeclarationConfiguration.ConfigurationName;
// Blazor-specific passes, in order.
if (!isDeclarationOnlyCompile)
{
// There's no benefit in this optimization during the declaration-only compile
builder.Features.Add(new TrimWhitespacePass());
}
builder.Features.Add(new ComponentDocumentClassifierPass());
builder.Features.Add(new ScriptTagPass());
builder.Features.Add(new ComplexAttributeContentPass());
builder.Features.Add(new ComponentLoweringPass());
builder.Features.Add(new EventHandlerLoweringPass());
builder.Features.Add(new ReferenceCaptureLoweringPass());
builder.Features.Add(new BindLoweringPass());
builder.Features.Add(new TemplateDiagnosticPass());
builder.Features.Add(new GenericComponentPass());
builder.Features.Add(new ChildContentDiagnosticPass());
builder.Features.Add(new HtmlBlockPass());
if (isDeclarationOnlyCompile)
{
// This is for 'declaration only' processing. We don't want to try and emit any method bodies during
// the design time build because we can't do it correctly until the set of components is known.
builder.Features.Add(new EliminateMethodBodyPass());
}
}
/// <summary>
/// Initializes the Blazor extension.
/// </summary>
/// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
public override void Initialize(RazorProjectEngineBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
Register(builder);
}
private class ConfigureBlazorCodeGenerationOptions : IConfigureRazorCodeGenerationOptionsFeature
{
public int Order => 0;
public RazorEngine Engine { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
// These metadata attributes require a reference to the Razor.Runtime package which we don't
// otherwise need.
options.SuppressMetadataAttributes = true;
}
}
}
}

View File

@ -1,647 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
// Copied directly from https://github.com/aspnet/Razor/blob/ff40124594b58b17988d50841175430a4b73d1a9/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriterExtensions.cs
// (other than the namespace change) because it's internal
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal static class CodeWriterExtensions
{
private const string InstanceMethodFormat = "{0}.{1}";
private static readonly char[] CStyleStringLiteralEscapeChars =
{
'\r',
'\t',
'\"',
'\'',
'\\',
'\0',
'\n',
'\u2028',
'\u2029',
};
public static bool IsAtBeginningOfLine(this CodeWriter writer)
{
return writer.Length == 0 || writer[writer.Length - 1] == '\n';
}
public static CodeWriter WritePadding(this CodeWriter writer, int offset, SourceSpan? span, CodeRenderingContext context)
{
if (span == null)
{
return writer;
}
var basePadding = CalculatePadding();
var resolvedPadding = Math.Max(basePadding - offset, 0);
if (context.Options.IndentWithTabs)
{
// Avoid writing directly to the StringBuilder here, that will throw off the manual indexing
// done by the base class.
var tabs = resolvedPadding / context.Options.IndentSize;
for (var i = 0; i < tabs; i++)
{
writer.Write("\t");
}
var spaces = resolvedPadding % context.Options.IndentSize;
for (var i = 0; i < spaces; i++)
{
writer.Write(" ");
}
}
else
{
for (var i = 0; i < resolvedPadding; i++)
{
writer.Write(" ");
}
}
return writer;
int CalculatePadding()
{
var spaceCount = 0;
for (var i = span.Value.AbsoluteIndex - 1; i >= 0; i--)
{
var @char = context.SourceDocument[i];
if (@char == '\n' || @char == '\r')
{
break;
}
else if (@char == '\t')
{
spaceCount += context.Options.IndentSize;
}
else
{
spaceCount++;
}
}
return spaceCount;
}
}
public static CodeWriter WriteVariableDeclaration(this CodeWriter writer, string type, string name, string value)
{
writer.Write(type).Write(" ").Write(name);
if (!string.IsNullOrEmpty(value))
{
writer.Write(" = ").Write(value);
}
else
{
writer.Write(" = null");
}
writer.WriteLine(";");
return writer;
}
public static CodeWriter WriteBooleanLiteral(this CodeWriter writer, bool value)
{
return writer.Write(value.ToString().ToLowerInvariant());
}
public static CodeWriter WriteStartAssignment(this CodeWriter writer, string name)
{
return writer.Write(name).Write(" = ");
}
public static CodeWriter WriteParameterSeparator(this CodeWriter writer)
{
return writer.Write(", ");
}
public static CodeWriter WriteStartNewObject(this CodeWriter writer, string typeName)
{
return writer.Write("new ").Write(typeName).Write("(");
}
public static CodeWriter WriteStringLiteral(this CodeWriter writer, string literal)
{
if (literal.Length >= 256 && literal.Length <= 1500 && literal.IndexOf('\0') == -1)
{
WriteVerbatimStringLiteral(writer, literal);
}
else
{
WriteCStyleStringLiteral(writer, literal);
}
return writer;
}
public static CodeWriter WriteUsing(this CodeWriter writer, string name)
{
return WriteUsing(writer, name, endLine: true);
}
public static CodeWriter WriteUsing(this CodeWriter writer, string name, bool endLine)
{
writer.Write("using ");
writer.Write(name);
if (endLine)
{
writer.WriteLine(";");
}
return writer;
}
public static CodeWriter WriteLineNumberDirective(this CodeWriter writer, SourceSpan span)
{
if (writer.Length >= writer.NewLine.Length && !IsAtBeginningOfLine(writer))
{
writer.WriteLine();
}
var lineNumberAsString = (span.LineIndex + 1).ToString(CultureInfo.InvariantCulture);
return writer.Write("#line ").Write(lineNumberAsString).Write(" \"").Write(span.FilePath).WriteLine("\"");
}
public static CodeWriter WriteStartMethodInvocation(this CodeWriter writer, string methodName)
{
writer.Write(methodName);
return writer.Write("(");
}
public static CodeWriter WriteEndMethodInvocation(this CodeWriter writer)
{
return WriteEndMethodInvocation(writer, endLine: true);
}
public static CodeWriter WriteEndMethodInvocation(this CodeWriter writer, bool endLine)
{
writer.Write(")");
if (endLine)
{
writer.WriteLine(";");
}
return writer;
}
// Writes a method invocation for the given instance name.
public static CodeWriter WriteInstanceMethodInvocation(
this CodeWriter writer,
string instanceName,
string methodName,
params string[] parameters)
{
if (instanceName == null)
{
throw new ArgumentNullException(nameof(instanceName));
}
if (methodName == null)
{
throw new ArgumentNullException(nameof(methodName));
}
return WriteInstanceMethodInvocation(writer, instanceName, methodName, endLine: true, parameters: parameters);
}
// Writes a method invocation for the given instance name.
public static CodeWriter WriteInstanceMethodInvocation(
this CodeWriter writer,
string instanceName,
string methodName,
bool endLine,
params string[] parameters)
{
if (instanceName == null)
{
throw new ArgumentNullException(nameof(instanceName));
}
if (methodName == null)
{
throw new ArgumentNullException(nameof(methodName));
}
return WriteMethodInvocation(
writer,
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName),
endLine,
parameters);
}
public static CodeWriter WriteStartInstanceMethodInvocation(this CodeWriter writer, string instanceName, string methodName)
{
if (instanceName == null)
{
throw new ArgumentNullException(nameof(instanceName));
}
if (methodName == null)
{
throw new ArgumentNullException(nameof(methodName));
}
return WriteStartMethodInvocation(
writer,
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName));
}
public static CodeWriter WriteField(this CodeWriter writer, IList<string> modifiers, string typeName, string fieldName)
{
if (modifiers == null)
{
throw new ArgumentNullException(nameof(modifiers));
}
if (typeName == null)
{
throw new ArgumentNullException(nameof(typeName));
}
if (fieldName == null)
{
throw new ArgumentNullException(nameof(fieldName));
}
for (var i = 0; i < modifiers.Count; i++)
{
writer.Write(modifiers[i]);
writer.Write(" ");
}
writer.Write(typeName);
writer.Write(" ");
writer.Write(fieldName);
writer.Write(";");
writer.WriteLine();
return writer;
}
public static CodeWriter WriteMethodInvocation(this CodeWriter writer, string methodName, params string[] parameters)
{
return WriteMethodInvocation(writer, methodName, endLine: true, parameters: parameters);
}
public static CodeWriter WriteMethodInvocation(this CodeWriter writer, string methodName, bool endLine, params string[] parameters)
{
return
WriteStartMethodInvocation(writer, methodName)
.Write(string.Join(", ", parameters))
.WriteEndMethodInvocation(endLine);
}
public static CodeWriter WriteAutoPropertyDeclaration(this CodeWriter writer, IList<string> modifiers, string typeName, string propertyName)
{
if (modifiers == null)
{
throw new ArgumentNullException(nameof(modifiers));
}
if (typeName == null)
{
throw new ArgumentNullException(nameof(typeName));
}
if (propertyName == null)
{
throw new ArgumentNullException(nameof(propertyName));
}
for (var i = 0; i < modifiers.Count; i++)
{
writer.Write(modifiers[i]);
writer.Write(" ");
}
writer.Write(typeName);
writer.Write(" ");
writer.Write(propertyName);
writer.Write(" { get; set; }");
writer.WriteLine();
return writer;
}
public static CSharpCodeWritingScope BuildScope(this CodeWriter writer)
{
return new CSharpCodeWritingScope(writer);
}
public static CSharpCodeWritingScope BuildLambda(this CodeWriter writer, params string[] parameterNames)
{
return BuildLambda(writer, async: false, parameterNames: parameterNames);
}
public static CSharpCodeWritingScope BuildAsyncLambda(this CodeWriter writer, params string[] parameterNames)
{
return BuildLambda(writer, async: true, parameterNames: parameterNames);
}
private static CSharpCodeWritingScope BuildLambda(CodeWriter writer, bool async, string[] parameterNames)
{
if (async)
{
writer.Write("async");
}
writer.Write("(").Write(string.Join(", ", parameterNames)).Write(") => ");
var scope = new CSharpCodeWritingScope(writer);
return scope;
}
public static CSharpCodeWritingScope BuildNamespace(this CodeWriter writer, string name)
{
writer.Write("namespace ").WriteLine(name);
return new CSharpCodeWritingScope(writer);
}
public static CSharpCodeWritingScope BuildClassDeclaration(
this CodeWriter writer,
IList<string> modifiers,
string name,
string baseType,
IEnumerable<string> interfaces)
{
for (var i = 0; i < modifiers.Count; i++)
{
writer.Write(modifiers[i]);
writer.Write(" ");
}
writer.Write("class ");
writer.Write(name);
var hasBaseType = !string.IsNullOrEmpty(baseType);
var hasInterfaces = interfaces != null && interfaces.Count() > 0;
if (hasBaseType || hasInterfaces)
{
writer.Write(" : ");
if (hasBaseType)
{
writer.Write(baseType);
if (hasInterfaces)
{
WriteParameterSeparator(writer);
}
}
if (hasInterfaces)
{
writer.Write(string.Join(", ", interfaces));
}
}
writer.WriteLine();
return new CSharpCodeWritingScope(writer);
}
public static CSharpCodeWritingScope BuildMethodDeclaration(
this CodeWriter writer,
string accessibility,
string returnType,
string name,
IEnumerable<KeyValuePair<string, string>> parameters)
{
writer.Write(accessibility)
.Write(" ")
.Write(returnType)
.Write(" ")
.Write(name)
.Write("(")
.Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value)))
.WriteLine(")");
return new CSharpCodeWritingScope(writer);
}
public static IDisposable BuildLinePragma(this CodeWriter writer, SourceSpan? span)
{
if (string.IsNullOrEmpty(span?.FilePath))
{
// Can't build a valid line pragma without a file path.
return NullDisposable.Default;
}
return new LinePragmaWriter(writer, span.Value);
}
private static void WriteVerbatimStringLiteral(CodeWriter writer, string literal)
{
writer.Write("@\"");
// We need to suppress indenting during the writing of the string's content. A
// verbatim string literal could contain newlines that don't get escaped.
var indent = writer.CurrentIndent;
writer.CurrentIndent = 0;
// We need to find the index of each '"' (double-quote) to escape it.
var start = 0;
int end;
while ((end = literal.IndexOf('\"', start)) > -1)
{
writer.Write(literal, start, end - start);
writer.Write("\"\"");
start = end + 1;
}
Debug.Assert(end == -1); // We've hit all of the double-quotes.
// Write the remainder after the last double-quote.
writer.Write(literal, start, literal.Length - start);
writer.Write("\"");
writer.CurrentIndent = indent;
}
private static void WriteCStyleStringLiteral(CodeWriter writer, string literal)
{
// From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM
writer.Write("\"");
// We need to find the index of each escapable character to escape it.
var start = 0;
int end;
while ((end = literal.IndexOfAny(CStyleStringLiteralEscapeChars, start)) > -1)
{
writer.Write(literal, start, end - start);
switch (literal[end])
{
case '\r':
writer.Write("\\r");
break;
case '\t':
writer.Write("\\t");
break;
case '\"':
writer.Write("\\\"");
break;
case '\'':
writer.Write("\\\'");
break;
case '\\':
writer.Write("\\\\");
break;
case '\0':
writer.Write("\\\0");
break;
case '\n':
writer.Write("\\n");
break;
case '\u2028':
case '\u2029':
writer.Write("\\u");
writer.Write(((int)literal[end]).ToString("X4", CultureInfo.InvariantCulture));
break;
default:
Debug.Assert(false, "Unknown escape character.");
break;
}
start = end + 1;
}
Debug.Assert(end == -1); // We've hit all of chars that need escaping.
// Write the remainder after the last escaped char.
writer.Write(literal, start, literal.Length - start);
writer.Write("\"");
}
public struct CSharpCodeWritingScope : IDisposable
{
private CodeWriter _writer;
private bool _autoSpace;
private int _tabSize;
private int _startIndent;
public CSharpCodeWritingScope(CodeWriter writer, int tabSize = 4, bool autoSpace = true)
{
_writer = writer;
_autoSpace = autoSpace;
_tabSize = tabSize;
_startIndent = -1; // Set in WriteStartScope
WriteStartScope();
}
public void Dispose()
{
WriteEndScope();
}
private void WriteStartScope()
{
TryAutoSpace(" ");
_writer.WriteLine("{");
_writer.CurrentIndent += _tabSize;
_startIndent = _writer.CurrentIndent;
}
private void WriteEndScope()
{
TryAutoSpace(_writer.NewLine);
// Ensure the scope hasn't been modified
if (_writer.CurrentIndent == _startIndent)
{
_writer.CurrentIndent -= _tabSize;
}
_writer.WriteLine("}");
}
private void TryAutoSpace(string spaceCharacter)
{
if (_autoSpace &&
_writer.Length > 0 &&
!char.IsWhiteSpace(_writer[_writer.Length - 1]))
{
_writer.Write(spaceCharacter);
}
}
}
private class LinePragmaWriter : IDisposable
{
private readonly CodeWriter _writer;
private readonly int _startIndent;
public LinePragmaWriter(CodeWriter writer, SourceSpan span)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
_writer = writer;
_startIndent = _writer.CurrentIndent;
_writer.CurrentIndent = 0;
WriteLineNumberDirective(writer, span);
}
public void Dispose()
{
// Need to add an additional line at the end IF there wasn't one already written.
// This is needed to work with the C# editor's handling of #line ...
var endsWithNewline = _writer.Length > 0 && _writer[_writer.Length - 1] == '\n';
// Always write at least 1 empty line to potentially separate code from pragmas.
_writer.WriteLine();
// Check if the previous empty line wasn't enough to separate code from pragmas.
if (!endsWithNewline)
{
_writer.WriteLine();
}
_writer
.WriteLine("#line default")
.WriteLine("#line hidden");
_writer.CurrentIndent = _startIndent;
}
}
private class NullDisposable : IDisposable
{
public static readonly NullDisposable Default = new NullDisposable();
private NullDisposable()
{
}
public void Dispose()
{
}
}
}
}

View File

@ -8,13 +8,18 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class BindLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentBindLoweringPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// Run after event handler pass
public override int Order => 100;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
var @namespace = documentNode.FindPrimaryNamespace();
var @class = documentNode.FindPrimaryClass();
if (@namespace == null || @class == null)

View File

@ -2,12 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ChildContentDiagnosticPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentChildContentDiagnosticPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// Runs after components/eventhandlers/ref/bind/templates. We want to validate every component
// and it's usage of ChildContent.
@ -15,6 +14,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
var visitor = new Visitor();
visitor.Visit(documentNode);
}

View File

@ -3,29 +3,28 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
/// <summary>
/// Directs a <see cref="DocumentWriter"/> to use <see cref="BlazorRuntimeNodeWriter"/>.
/// </summary>
internal class BlazorCodeTarget : CodeTarget
internal class ComponentCodeTarget : CodeTarget
{
private readonly RazorCodeGenerationOptions _options;
public BlazorCodeTarget(RazorCodeGenerationOptions options, IEnumerable<ICodeTargetExtension> extensions)
public ComponentCodeTarget(RazorCodeGenerationOptions options, IEnumerable<ICodeTargetExtension> extensions)
{
_options = options;
Extensions = extensions.ToArray();
// Components provide some built-in target extensions that don't apply to
// legacy documents.
Extensions = new[] { new ComponentTemplateTargetExtension(), }.Concat(extensions).ToArray();
}
public ICodeTargetExtension[] Extensions { get; }
public override IntermediateNodeWriter CreateNodeWriter()
{
return _options.DesignTime ? (BlazorNodeWriter)new BlazorDesignTimeNodeWriter() : new BlazorRuntimeNodeWriter();
return _options.DesignTime ? (IntermediateNodeWriter)new ComponentDesignTimeNodeWriter() : new ComponentRuntimeNodeWriter();
}
public override TExtension GetExtension<TExtension>()

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.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
@ -12,13 +11,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
//
// This is where a lot of the complexity in the Razor/TagHelpers model creeps in and we
// might be able to avoid it if these features aren't needed.
internal class ComplexAttributeContentPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentComplexAttributeContentPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// Run before other Blazor passes
// Run before other Component passes
public override int Order => -1000;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
var nodes = documentNode.FindDescendantNodes<TagHelperIntermediateNode>();
for (var i = 0; i < nodes.Count; i++)
{

View File

@ -11,13 +11,13 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
// Based on the DesignTimeNodeWriter from Razor repo.
internal class BlazorDesignTimeNodeWriter : BlazorNodeWriter
internal class ComponentDesignTimeNodeWriter : ComponentNodeWriter
{
private readonly ScopeStack _scopeStack = new ScopeStack();
private static readonly string DesignTimeVariable = "__o";
public override void WriteHtmlBlock(CodeRenderingContext context, MarkupBlockIntermediateNode node)
public override void WriteMarkupBlock(CodeRenderingContext context, MarkupBlockIntermediateNode node)
{
if (context == null)
{
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Do nothing
}
public override void WriteHtmlElement(CodeRenderingContext context, MarkupElementIntermediateNode node)
public override void WriteMarkupElement(CodeRenderingContext context, MarkupElementIntermediateNode node)
{
if (context == null)
{
@ -309,7 +309,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Do nothing
}
public override void BeginWriteAttribute(CodeWriter codeWriter, string key)
protected override void BeginWriteAttribute(CodeWriter codeWriter, string key)
{
if (codeWriter == null)
{

View File

@ -3,7 +3,9 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
@ -25,6 +27,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return string.Equals(codeDocument.GetFileKind(), FileKinds.Component);
}
protected override CodeTarget CreateTarget(RazorCodeDocument codeDocument, RazorCodeGenerationOptions options)
{
return new ComponentCodeTarget(options, TargetExtensions);
}
protected override void OnDocumentStructureCreated(RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method)
{
base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
@ -45,7 +52,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
@namespace.Content = computedNamespace;
@class.ClassName = computedClass;
@class.BaseType = $"global::{CodeGenerationConstants.ComponentBase.FullTypeName}";
@class.BaseType = $"{CodeGenerationConstants.ComponentBase.FullTypeName}";
var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
if (string.IsNullOrEmpty(filePath))
{
@ -61,12 +68,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
@class.Modifiers.Clear();
@class.Modifiers.Add("public");
@class.Modifiers.Add("sealed");
var documentNode = codeDocument.GetDocumentIntermediateNode();
var typeParamReferences = documentNode.FindDirectiveReferences(ComponentTypeParamDirective.Directive);
for (var i = 0; i < typeParamReferences.Count; i++)
{
var typeParamNode = (DirectiveIntermediateNode)typeParamReferences[i].Node;
if (typeParamNode.HasDiagnostics)
{
continue;
}
@class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, });
}
method.MethodName = CodeGenerationConstants.ComponentBase.BuildRenderTree;
method.ReturnType = "void";
method.Modifiers.Clear();
method.Modifiers.Add("public");
method.Modifiers.Add("protected");
method.Modifiers.Add("override");
method.Parameters.Clear();

View File

@ -8,12 +8,17 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class EventHandlerLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentEventHandlerLoweringPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
public override int Order => 50;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
var @namespace = documentNode.FindPrimaryNamespace();
var @class = documentNode.FindPrimaryClass();
if (@namespace == null || @class == null)

View File

@ -1,22 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
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

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// 1. Adds diagnostics for missing generic type arguments
// 2. Rewrites the type name of the component to substitute generic type arguments
// 3. Rewrites the type names of parameters/child content to substitute generic type arguments
internal class GenericComponentPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentGenericTypePass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
private TypeNameFeature _typeNameFeature;
@ -19,27 +19,42 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// and it's usage of ChildContent.
public override int Order => 160;
protected override void OnInitialized()
private TypeNameFeature TypeNameFeature
{
_typeNameFeature = GetRequiredFeature<TypeNameFeature>();
get
{
// Doing lazy intialization here to avoid making things really complicated when we don't
// need to exercise this code in tests.
if (_typeNameFeature == null)
{
_typeNameFeature = GetRequiredFeature<TypeNameFeature>();
}
return _typeNameFeature;
}
}
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
var visitor = new Visitor(_typeNameFeature);
if (!IsComponentDocument(documentNode))
{
return;
}
var visitor = new Visitor(this);
visitor.Visit(documentNode);
}
private class Visitor : IntermediateNodeWalker
{
private readonly TypeNameFeature _typeNameFeature;
private readonly ComponentGenericTypePass _pass;
// Incrementing ID for type inference method names
private int _id;
public Visitor(TypeNameFeature typeNameFeature)
public Visitor(ComponentGenericTypePass pass)
{
_typeNameFeature = typeNameFeature;
_pass = pass;
}
public override void VisitComponent(ComponentIntermediateNode node)
@ -82,7 +97,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
if (ValidateTypeArguments(node, bindings))
{
var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content);
RewriteTypeNames(_typeNameFeature.CreateGenericTypeRewriter(mappings), node);
RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), node);
}
return;
@ -96,7 +111,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Since we're generating code in a different namespace, we need to 'global qualify' all of the types
// to avoid clashes with our generated code.
RewriteTypeNames(_typeNameFeature.CreateGlobalQualifiedTypeNameRewriter(bindings.Keys), node);
RewriteTypeNames(_pass.TypeNameFeature.CreateGlobalQualifiedTypeNameRewriter(bindings.Keys), node);
//
// We need to verify that an argument was provided that 'covers' each type parameter.
@ -122,7 +137,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
continue;
}
var typeParameters = _typeNameFeature.ParseTypeParameters(attribute.TypeName);
var typeParameters = _pass.TypeNameFeature.ParseTypeParameters(attribute.TypeName);
if (typeParameters.Count == 0)
{
bindings.Remove(attribute.TypeName);
@ -145,7 +160,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// helpful as possible. So let's substitute 'object' for all of those type parameters, and add
// an error.
var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content);
RewriteTypeNames(_typeNameFeature.CreateGenericTypeRewriter(mappings), node);
RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), node);
node.Diagnostics.Add(ComponentDiagnosticFactory.Create_GenericComponentTypeInferenceUnderspecified(node.Source, node, node.Component.GetTypeParameters()));
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
//
// Does not preserve insignificant details of the HTML, like tag closing style
// or quote style.
internal class HtmlBlockPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentHtmlBlockPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// Runs LATE because we want to destroy structure.
public override int Order => 10000;
@ -23,6 +23,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
RazorCodeDocument codeDocument,
DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
if (documentNode.Options.DesignTime)
{
// Nothing to do during design time.
@ -114,9 +119,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// We would store both the div and a tag in a list, but make sure to visit
// the div first. Then when we process the div (recursively), we would remove
// the a from the list.
private class FindHtmlTreeVisitor :
IntermediateNodeWalker,
IExtensionIntermediateNodeVisitor<MarkupElementIntermediateNode>
private class FindHtmlTreeVisitor : IntermediateNodeWalker
{
private bool _foundNonHtml;
@ -129,7 +132,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
base.VisitDefault(node);
}
public void VisitExtension(MarkupElementIntermediateNode node)
public override void VisitMarkupElement(MarkupElementIntermediateNode node)
{
// We need to restore the state after processing this node.
// We might have found a leaf-block of HTML, but that shouldn't
@ -226,9 +229,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
}
private class RewriteVisitor :
IntermediateNodeWalker,
IExtensionIntermediateNodeVisitor<MarkupElementIntermediateNode>
private class RewriteVisitor : IntermediateNodeWalker
{
private readonly StringBuilder _encodingBuilder;
@ -243,7 +244,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public StringBuilder Builder { get; } = new StringBuilder();
public void VisitExtension(MarkupElementIntermediateNode node)
public override void VisitMarkupElement(MarkupElementIntermediateNode node)
{
for (var i = 0; i < _trees.Count; i++)
{
@ -319,12 +320,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public override void VisitHtmlAttributeValue(HtmlAttributeValueIntermediateNode node)
{
Builder.Append(node.Children);
for (var i = 0; i < node.Children.Count; i++)
{
if (node.Children[i] is IntermediateToken token)
{
Builder.Append(token.Content);
}
}
}
public override void VisitHtml(HtmlContentIntermediateNode node)
{
Builder.Append(node.Children);
for (var i = 0; i < node.Children.Count; i++)
{
if (node.Children[i] is IntermediateToken token)
{
Builder.Append(token.Content);
}
}
}
}
}

View File

@ -9,7 +9,7 @@ using System.Text;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class BlazorImportProjectFeature : IImportProjectFeature
internal class ComponentImportProjectFeature : IImportProjectFeature
{
private const string ImportsFileName = "_ViewImports.cshtml";
@ -33,6 +33,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
throw new ArgumentNullException(nameof(projectItem));
}
// Don't add Component imports for a non-component.
if (!string.Equals(projectItem.FileKind, FileKinds.Component, StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<RazorProjectItem>();
}
var imports = new List<RazorProjectItem>()
{
new VirtualProjectItem(DefaultUsingImportContent),
@ -46,13 +52,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Avoiding the path-specific APIs here, we want to handle all styles of paths
// on all platforms
var trimLength = projectItem.FilePath.Length + (projectItem.FilePath.StartsWith("/") ? 0 : 1);
var baseDirectory = projectItem.PhysicalPath.Substring(0, projectItem.PhysicalPath.Length - trimLength);
var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators);
var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1);
if (!string.IsNullOrEmpty(baseNamespace))
if (projectItem.PhysicalPath.Length > trimLength)
{
imports.Add(new VirtualProjectItem($@"@addTagHelper ""*, {baseNamespace}"""));
var baseDirectory = projectItem.PhysicalPath.Substring(0, projectItem.PhysicalPath.Length - trimLength);
var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators);
var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1);
if (!string.IsNullOrEmpty(baseNamespace))
{
imports.Add(new VirtualProjectItem($@"@addTagHelper ""*, {baseNamespace}"""));
}
}
}

View File

@ -0,0 +1,36 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
// Much of the following is equivalent to Microsoft.AspNetCore.Mvc.Razor.Extensions's InjectDirective,
// but this one outputs properties annotated for Blazor's property injector, plus it doesn't need to
// support multiple CodeTargets.
internal class ComponentInjectDirective
{
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
"inject",
DirectiveKind.SingleLine,
builder =>
{
builder.AddTypeToken("TypeName", "The type of the service to inject.");
builder.AddMemberToken("PropertyName", "The name of the property.");
builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
builder.Description = "Inject a service from the application's service container into a property.";
});
public static void Register(RazorProjectEngineBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddDirective(FileKinds.Component, Directive);
builder.Features.Add(new ComponentInjectDirectivePass());
}
}
}

View File

@ -0,0 +1,58 @@
// 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 Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ComponentInjectDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{
protected override void ExecuteCore(
RazorCodeDocument codeDocument,
DocumentIntermediateNode documentNode)
{
var visitor = new Visitor();
visitor.Visit(documentNode);
var properties = new HashSet<string>(StringComparer.Ordinal);
var classNode = documentNode.FindPrimaryClass();
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
{
var directive = visitor.Directives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length < 2)
{
continue;
}
var typeName = tokens[0].Content;
var memberName = tokens[1].Content;
if (!properties.Add(memberName))
{
continue;
}
classNode.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName));
}
}
private class Visitor : IntermediateNodeWalker
{
public IList<DirectiveIntermediateNode> Directives { get; }
= new List<DirectiveIntermediateNode>();
public override void VisitDirective(DirectiveIntermediateNode node)
{
if (node.Directive == ComponentInjectDirective.Directive)
{
Directives.Add(node);
}
}
}
}
}

View File

@ -0,0 +1,60 @@
// 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.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ComponentInjectIntermediateNode : ExtensionIntermediateNode
{
private static readonly IList<string> _injectedPropertyModifiers = new[]
{
$"[global::{ComponentsApi.InjectAttribute.FullTypeName}]",
"private" // Encapsulation is the default
};
public ComponentInjectIntermediateNode(string typeName, string memberName)
{
TypeName = typeName;
MemberName = memberName;
}
public string TypeName { get; }
public string MemberName { get; }
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
AcceptExtensionNode(this, visitor);
}
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
{
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.CodeWriter.WriteAutoPropertyDeclaration(
_injectedPropertyModifiers,
TypeName,
MemberName);
}
}
}

View File

@ -0,0 +1,21 @@
// 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.Components
{
internal abstract class ComponentIntermediateNodePassBase : IntermediateNodePassBase
{
protected bool IsComponentDocument(DocumentIntermediateNode document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
return string.Equals(document.DocumentKind, ComponentDocumentClassifierPass.ComponentDocumentKind, StringComparison.Ordinal);
}
}
}

View File

@ -2,11 +2,10 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal static class LayoutDirective
internal static class ComponentLayoutDirective
{
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
"layout",
@ -25,8 +24,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
throw new ArgumentNullException(nameof(builder));
}
builder.AddDirective(Directive);
builder.Features.Add(new LayoutDirectivePass());
builder.AddDirective(FileKinds.Component, Directive);
builder.Features.Add(new ComponentLayoutDirectivePass());
}
}
}

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class LayoutDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
internal class ComponentLayoutDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return;
}
var directives = documentNode.FindDirectiveReferences(LayoutDirective.Directive);
var directives = documentNode.FindDirectiveReferences(ComponentLayoutDirective.Directive);
if (directives.Count == 0)
{
return;

View File

@ -7,13 +7,18 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ComponentLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentLoweringPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// This pass runs earlier than our other passes that 'lower' specific kinds of attributes.
public override int Order => 0;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
var @namespace = documentNode.FindPrimaryNamespace();
var @class = documentNode.FindPrimaryClass();
if (@namespace == null || @class == null)

View File

@ -10,23 +10,9 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal abstract class BlazorNodeWriter : IntermediateNodeWriter
internal abstract class ComponentNodeWriter : IntermediateNodeWriter, ITemplateTargetExtension
{
public abstract void BeginWriteAttribute(CodeWriter codeWriter, string key);
public abstract void WriteComponent(CodeRenderingContext context, ComponentIntermediateNode node);
public abstract void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeIntermediateNode node);
public abstract void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node);
public abstract void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentIntermediateNode node);
public abstract void WriteHtmlElement(CodeRenderingContext context, MarkupElementIntermediateNode node);
public abstract void WriteHtmlBlock(CodeRenderingContext context, MarkupBlockIntermediateNode node);
public abstract void WriteReferenceCapture(CodeRenderingContext context, ReferenceCaptureIntermediateNode node);
protected abstract void BeginWriteAttribute(CodeWriter codeWriter, string key);
protected abstract void WriteReferenceCaptureInnards(CodeRenderingContext context, ReferenceCaptureIntermediateNode node, bool shouldTypeCheck);
@ -53,9 +39,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return;
}
// Currently the same for design time and runtime
public void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node)
public override void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node)
{
if (context == null)
{

View File

@ -2,12 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class PageDirective
internal class ComponentPageDirective
{
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
"page",
@ -19,7 +18,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
builder.Description = ComponentResources.PageDirective_Description;
});
private PageDirective(string routeTemplate, IntermediateNode directiveNode)
private ComponentPageDirective(string routeTemplate, IntermediateNode directiveNode)
{
RouteTemplate = routeTemplate;
DirectiveNode = directiveNode;
@ -36,8 +35,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
throw new ArgumentNullException(nameof(builder));
}
builder.AddDirective(Directive);
builder.Features.Add(new PageDirectivePass());
builder.AddDirective(FileKinds.Component, Directive);
builder.Features.Add(new ComponentPageDirectivePass());
return builder;
}
}

View File

@ -3,13 +3,11 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class PageDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
internal class ComponentPageDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
@ -30,7 +28,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return;
}
var directives = documentNode.FindDirectiveReferences(PageDirective.Directive);
var directives = documentNode.FindDirectiveReferences(ComponentPageDirective.Directive);
if (directives.Count == 0)
{
return;

View File

@ -1,18 +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 Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ReferenceCaptureLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentReferenceCaptureLoweringPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// Run after component lowering pass
public override int Order => 50;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
var @namespace = documentNode.FindPrimaryNamespace();
var @class = documentNode.FindPrimaryClass();
if (@namespace == null || @class == null)

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
/// <summary>
/// Generates the C# code corresponding to Razor source document contents.
/// </summary>
internal class BlazorRuntimeNodeWriter : BlazorNodeWriter
internal class ComponentRuntimeNodeWriter : ComponentNodeWriter
{
private readonly List<IntermediateToken> _currentAttributeValues = new List<IntermediateToken>();
private readonly ScopeStack _scopeStack = new ScopeStack();
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
}
public override void WriteHtmlBlock(CodeRenderingContext context, MarkupBlockIntermediateNode node)
public override void WriteMarkupBlock(CodeRenderingContext context, MarkupBlockIntermediateNode node)
{
if (context == null)
{
@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
.WriteEndMethodInvocation();
}
public override void WriteHtmlElement(CodeRenderingContext context, MarkupElementIntermediateNode node)
public override void WriteMarkupElement(CodeRenderingContext context, MarkupElementIntermediateNode node)
{
if (context == null)
{
@ -641,7 +641,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
});
}
}
private void WriteAttribute(CodeWriter codeWriter, string key, IList<IntermediateToken> value)
{
BeginWriteAttribute(codeWriter, key);
@ -649,7 +649,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
codeWriter.WriteEndMethodInvocation();
}
public override void BeginWriteAttribute(CodeWriter codeWriter, string key)
protected override void BeginWriteAttribute(CodeWriter codeWriter, string key)
{
codeWriter
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddAttribute)}")

View File

@ -6,14 +6,14 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class ScriptTagPass : IntermediateNodePassBase, IRazorDocumentClassifierPass
internal class ComponentScriptTagPass : ComponentIntermediateNodePassBase, IRazorDocumentClassifierPass
{
// Run as soon as possible after the Component rewrite pass
public override int Order => ComponentDocumentClassifierPass.DefaultFeatureOrder + 2;
public override int Order => 5;
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (documentNode.DocumentKind != ComponentDocumentClassifierPass.ComponentDocumentKind)
if (!IsComponentDocument(documentNode))
{
return;
}
@ -22,9 +22,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
visitor.Visit(documentNode);
}
private class Visitor : IntermediateNodeWalker, IExtensionIntermediateNodeVisitor<MarkupElementIntermediateNode>
private class Visitor : IntermediateNodeWalker
{
public void VisitExtension(MarkupElementIntermediateNode node)
public override void VisitMarkupElement(MarkupElementIntermediateNode node)
{
// Disallow <script> in components as per #552
if (string.Equals(node.TagName, "script", StringComparison.OrdinalIgnoreCase))

View File

@ -1,14 +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 Microsoft.AspNetCore.Razor.Language;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class TemplateDiagnosticPass : IntermediateNodePassBase, IRazorOptimizationPass
internal class ComponentTemplateDiagnosticPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
// Runs after components/eventhandlers/ref/bind. We need to check for templates in all of those
// places.
@ -16,6 +15,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
if (!IsComponentDocument(documentNode))
{
return;
}
var visitor = new Visitor();
visitor.Visit(documentNode);

View File

@ -6,11 +6,13 @@ using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class BlazorTemplateTargetExtension : ITemplateTargetExtension
internal class ComponentTemplateTargetExtension : ITemplateTargetExtension
{
public void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node)
{
((BlazorNodeWriter)context.NodeWriter).WriteTemplate(context, node);
// This is OK because this will only be plugged in by the component code target
// not globally.
((ComponentNodeWriter)context.NodeWriter).WriteTemplate(context, node);
}
}
}

View File

@ -2,11 +2,10 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class TypeParamDirective
internal class ComponentTypeParamDirective
{
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
"typeparam",
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
throw new ArgumentNullException(nameof(builder));
}
builder.AddDirective(Directive);
builder.AddDirective(FileKinds.Component, Directive);
return builder;
}
}

View File

@ -1,13 +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.
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using System;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal class TrimWhitespacePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
internal class ComponentWhitespacePass : ComponentIntermediateNodePassBase, IRazorDirectiveClassifierPass
{
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
@ -21,15 +20,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
throw new ArgumentNullException(nameof(documentNode));
}
// There's no benefit running the whitespace trimmer during design-time builds
if (!documentNode.Options.DesignTime)
if (!IsComponentDocument(documentNode))
{
var method = documentNode.FindPrimaryMethod();
if (method != null)
{
RemoveContiguousWhitespace(method.Children, TraversalDirection.Forwards);
RemoveContiguousWhitespace(method.Children, TraversalDirection.Backwards);
}
return;
}
if (documentNode.Options.SuppressPrimaryMethodBody)
{
// There's no benefit running the whitespace trimmer if we're not emitting
// the method bodies.
return;
}
// There's no benefit running the whitespace trimmer during design-time builds
if (documentNode.Options.DesignTime)
{
return;
}
var method = documentNode.FindPrimaryMethod();
if (method != null)
{
RemoveContiguousWhitespace(method.Children, TraversalDirection.Forwards);
RemoveContiguousWhitespace(method.Children, TraversalDirection.Backwards);
}
}

View File

@ -1,111 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
// Much of the following is equivalent to Microsoft.AspNetCore.Mvc.Razor.Extensions's InjectDirective,
// but this one outputs properties annotated for Blazor's property injector, plus it doesn't need to
// support multiple CodeTargets.
internal class InjectDirective
{
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
"inject",
DirectiveKind.SingleLine,
builder =>
{
builder.AddTypeToken("TypeName", "The type of the service to inject.");
builder.AddMemberToken("PropertyName", "The name of the property.");
builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
builder.Description = "Inject a service from the application's service container into a property.";
});
public static void Register(RazorProjectEngineBuilder builder)
{
builder.AddDirective(Directive);
builder.Features.Add(new Pass());
}
private class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{
protected override void ExecuteCore(
RazorCodeDocument codeDocument,
DocumentIntermediateNode documentNode)
{
var visitor = new Visitor();
visitor.Visit(documentNode);
var properties = new HashSet<string>(StringComparer.Ordinal);
var classNode = documentNode.FindPrimaryClass();
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
{
var directive = visitor.Directives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length < 2)
{
continue;
}
var typeName = tokens[0].Content;
var memberName = tokens[1].Content;
if (!properties.Add(memberName))
{
continue;
}
classNode.Children.Add(new InjectIntermediateNode(typeName, memberName));
}
}
private class Visitor : IntermediateNodeWalker
{
public IList<DirectiveIntermediateNode> Directives { get; }
= new List<DirectiveIntermediateNode>();
public override void VisitDirective(DirectiveIntermediateNode node)
{
if (node.Directive == Directive)
{
Directives.Add(node);
}
}
}
internal class InjectIntermediateNode : ExtensionIntermediateNode
{
private static readonly IList<string> _injectedPropertyModifiers = new[]
{
$"[global::{ComponentsApi.InjectAttribute.FullTypeName}]",
"private" // Encapsulation is the default
};
public string TypeName { get; }
public string MemberName { get; }
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public InjectIntermediateNode(string typeName, string memberName)
{
TypeName = typeName;
MemberName = memberName;
}
public override void Accept(IntermediateNodeVisitor visitor)
=> AcceptExtensionNode(this, visitor);
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
=> context.CodeWriter.WriteAutoPropertyDeclaration(
_injectedPropertyModifiers,
TypeName,
MemberName);
}
}
}
}

View File

@ -1,29 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language;
using System;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
/// <summary>
/// Represents a fatal error during the transformation of a Blazor component from
/// Razor source code to C# source code.
/// </summary>
public class RazorCompilerException : Exception
{
/// <summary>
/// Constructs an instance of <see cref="RazorCompilerException"/>.
/// </summary>
/// <param name="diagnostic"></param>
public RazorCompilerException(RazorDiagnostic diagnostic)
{
Diagnostic = diagnostic;
}
/// <summary>
/// Gets the diagnostic value.
/// </summary>
public RazorDiagnostic Diagnostic { get; }
}
}

View File

@ -9,12 +9,15 @@ namespace Microsoft.AspNetCore.Razor.Language
{
private bool _designTime;
public DefaultRazorCodeGenerationOptionsBuilder(RazorConfiguration configuration)
public DefaultRazorCodeGenerationOptionsBuilder(RazorConfiguration configuration, string fileKind)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
Configuration = configuration;
FileKind = fileKind;
}
public DefaultRazorCodeGenerationOptionsBuilder(bool designTime)
@ -26,6 +29,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public override bool DesignTime => _designTime;
public override string FileKind { get; }
public override int IndentSize { get; set; } = 4;
public override bool IndentWithTabs { get; set; }

View File

@ -15,9 +15,9 @@ namespace Microsoft.AspNetCore.Razor.Language
_configureOptions = ProjectEngine.EngineFeatures.OfType<IConfigureRazorCodeGenerationOptionsFeature>().ToArray();
}
public RazorCodeGenerationOptions Create(Action<RazorCodeGenerationOptionsBuilder> configure)
public RazorCodeGenerationOptions Create(string fileKind, Action<RazorCodeGenerationOptionsBuilder> configure)
{
var builder = new DefaultRazorCodeGenerationOptionsBuilder(ProjectEngine.Configuration);
var builder = new DefaultRazorCodeGenerationOptionsBuilder(ProjectEngine.Configuration, fileKind);
configure?.Invoke(builder);
for (var i = 0; i < _configureOptions.Length; i++)

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
@ -10,6 +11,8 @@ namespace Microsoft.AspNetCore.Razor.Language
{
public ICollection<DirectiveDescriptor> Directives { get; } = new List<DirectiveDescriptor>();
public IDictionary<string, ICollection<DirectiveDescriptor>> DirectivesByFileKind { get; } = new Dictionary<string, ICollection<DirectiveDescriptor>>(StringComparer.OrdinalIgnoreCase);
public int Order => 100;
void IConfigureRazorParserOptionsFeature.Configure(RazorParserOptionsBuilder options)
@ -25,6 +28,21 @@ namespace Microsoft.AspNetCore.Razor.Language
{
options.Directives.Add(directive);
}
if (options.FileKind != null && DirectivesByFileKind.TryGetValue(options.FileKind, out var directives))
{
foreach (var directive in directives)
{
// Replace any non-file-kind-specific directives
var replaces = options.Directives.Where(d => string.Equals(d.Directive, directive.Directive, StringComparison.Ordinal)).ToArray();
foreach (var replace in replaces)
{
options.Directives.Remove(replace);
}
options.Directives.Add(directive);
}
}
}
}
}

View File

@ -35,32 +35,34 @@ namespace Microsoft.AspNetCore.Razor.Language
document.Options = codeDocument.GetCodeGenerationOptions() ?? _optionsFeature.GetOptions();
IReadOnlyList<UsingReference> importedUsings = Array.Empty<UsingReference>();
// The import documents should be inserted logically before the main document.
var imports = codeDocument.GetImportSyntaxTrees();
if (imports != null)
{
var importsVisitor = new ImportsVisitor(document, builder, syntaxTree.Options.FeatureFlags);
var importedUsings = ImportDirectives(document, builder, syntaxTree.Options, imports);
for (var j = 0; j < imports.Count; j++)
// Lower the main document, appending after the imported directives.
//
// We need to decide up front if this document is a "component" file. This will affect how
// lowering behaves.
LoweringVisitor visitor;
if (string.Equals(FileKinds.Component, codeDocument.GetFileKind(), StringComparison.OrdinalIgnoreCase) &&
syntaxTree.Options.FeatureFlags.AllowComponentFileKind)
{
visitor = new ComponentFileKindVisitor(document, builder, syntaxTree.Options.FeatureFlags)
{
var import = imports[j];
SourceDocument = syntaxTree.Source,
};
importsVisitor.SourceDocument = import.Source;
importsVisitor.Visit(import.Root);
}
importedUsings = importsVisitor.Usings;
visitor.Visit(syntaxTree.Root);
}
var tagHelperPrefix = tagHelperContext?.Prefix;
var visitor = new MainSourceVisitor(document, builder, tagHelperPrefix, syntaxTree.Options.FeatureFlags)
else
{
SourceDocument = syntaxTree.Source,
};
visitor = new LegacyFileKindVisitor(document, builder, tagHelperContext?.Prefix, syntaxTree.Options.FeatureFlags)
{
SourceDocument = syntaxTree.Source,
};
visitor.Visit(syntaxTree.Root);
visitor.Visit(syntaxTree.Root);
}
// 1. Prioritize non-imported usings over imported ones.
// 2. Don't import usings that already exist in primary document.
@ -76,7 +78,6 @@ namespace Microsoft.AspNetCore.Razor.Language
// In each lowering piece above, namespaces were tracked. We render them here to ensure every
// lowering action has a chance to add a source location to a namespace. Ultimately, closest wins.
var i = 0;
foreach (var reference in usingReferences)
{
@ -89,7 +90,7 @@ namespace Microsoft.AspNetCore.Razor.Language
builder.Insert(i++, @using);
}
ImportDirectives(document);
PostProcessImportedDirectives(document);
// The document should contain all errors that currently exist in the system. This involves
// adding the errors from the primary and imported syntax trees.
@ -113,15 +114,36 @@ namespace Microsoft.AspNetCore.Razor.Language
codeDocument.SetDocumentIntermediateNode(document);
}
private void ImportDirectives(DocumentIntermediateNode document)
private IReadOnlyList<UsingReference> ImportDirectives(
DocumentIntermediateNode document,
IntermediateNodeBuilder builder,
RazorParserOptions options,
IReadOnlyList<RazorSyntaxTree> imports)
{
var visitor = new DirectiveVisitor();
visitor.VisitDocument(document);
var seenDirectives = new HashSet<DirectiveDescriptor>();
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
if (imports == null)
{
var reference = visitor.Directives[i];
return Array.Empty<UsingReference>();
}
var importsVisitor = new ImportsVisitor(document, builder, options.FeatureFlags);
for (var i = 0; i < imports.Count; i++)
{
var import = imports[i];
importsVisitor.SourceDocument = import.Source;
importsVisitor.Visit(import.Root);
}
return importsVisitor.Usings;
}
private void PostProcessImportedDirectives(DocumentIntermediateNode document)
{
var directives = document.FindDescendantReferences<DirectiveIntermediateNode>();
var seenDirectives = new HashSet<DirectiveDescriptor>();
for (var i = directives.Count - 1; i >= 0; i--)
{
var reference = directives[i];
var directive = (DirectiveIntermediateNode)reference.Node;
var descriptor = directive.Directive;
var seenDirective = !seenDirectives.Add(descriptor);
@ -134,24 +156,29 @@ namespace Microsoft.AspNetCore.Razor.Language
switch (descriptor.Kind)
{
case DirectiveKind.SingleLine:
if (seenDirective && descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
{
// This directive has been overridden, it should be removed from the document.
if (seenDirective && descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
{
// This directive has been overridden, it should be removed from the document.
break;
}
continue;
}
case DirectiveKind.RazorBlock:
case DirectiveKind.CodeBlock:
{
if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
{
// A block directive cannot be imported.
document.Diagnostics.Add(
RazorDiagnosticFactory.CreateDirective_BlockDirectiveCannotBeImported(descriptor.Directive));
}
break;
}
continue;
case DirectiveKind.RazorBlock:
case DirectiveKind.CodeBlock:
if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
{
// A block directive cannot be imported.
document.Diagnostics.Add(
RazorDiagnosticFactory.CreateDirective_BlockDirectiveCannotBeImported(descriptor.Directive));
}
break;
default:
throw new InvalidOperationException(Resources.FormatUnexpectedDirectiveKind(typeof(DirectiveKind).FullName));
}
@ -406,12 +433,13 @@ namespace Microsoft.AspNetCore.Razor.Language
}
}
private class MainSourceVisitor : LoweringVisitor
// Lowers a document using *html-as-text* and Tag Helpers
private class LegacyFileKindVisitor : LoweringVisitor
{
private readonly HashSet<string> _renderedBoundAttributeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly string _tagHelperPrefix;
public MainSourceVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, string tagHelperPrefix, RazorParserFeatureFlags featureFlags)
public LegacyFileKindVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, string tagHelperPrefix, RazorParserFeatureFlags featureFlags)
: base(document, builder, featureFlags)
{
_tagHelperPrefix = tagHelperPrefix;
@ -1057,6 +1085,665 @@ namespace Microsoft.AspNetCore.Razor.Language
}
}
// Lowers a document using *html-as-nodes* and Components
private class ComponentFileKindVisitor : LoweringVisitor
{
private readonly HashSet<string> _renderedBoundAttributeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public ComponentFileKindVisitor(
DocumentIntermediateNode document,
IntermediateNodeBuilder builder,
RazorParserFeatureFlags featureFlags)
: base(document, builder, featureFlags)
{
}
public override void DefaultVisit(SyntaxNode node)
{
base.DefaultVisit(node);
}
public override void VisitMarkupElement(MarkupElementSyntax node)
{
_builder.Push(new MarkupElementIntermediateNode()
{
Source = BuildSourceSpanFromNode(node),
// Could be empty while the tag is being typed in.
TagName = node.StartTag?.GetTagName() ?? node.EndTag?.GetTagName() ?? string.Empty,
});
base.VisitMarkupElement(node);
_builder.Pop();
}
public override void VisitMarkupStartTag(MarkupStartTagSyntax node)
{
// We want to skip over the other misc tokens that make up a start tag, and
// just process the attributes.
//
// Visit the attributes
foreach (var block in node.Children)
{
if (block is MarkupAttributeBlockSyntax attribute)
{
VisitMarkupAttributeBlock(attribute);
}
else if (block is MarkupMinimizedAttributeBlockSyntax minimized)
{
VisitMarkupMinimizedAttributeBlock(minimized);
}
}
}
public override void VisitMarkupEndTag(MarkupEndTagSyntax node)
{
// We want to skip over the other misc tokens that make up a start tag, and
// just process the attributes.
//
// Nothing to do here
}
// Example
// <input` checked="hello-world @false"`/>
// Name=checked
// Prefix= checked="
// Suffix="
public override void VisitMarkupAttributeBlock(MarkupAttributeBlockSyntax node)
{
// For now we're using HtmlAttributeIntermediateNode for these so we're still
// building Prefix and Suffix, even though we don't really use them. If we
// end up using another node type in the future this can be simplified quite
// a lot.
var prefixTokens = MergeLiterals(
node.NamePrefix?.LiteralTokens,
node.Name.LiteralTokens,
node.NameSuffix?.LiteralTokens,
node.EqualsToken == null ? new SyntaxList<SyntaxToken>() : new SyntaxList<SyntaxToken>(node.EqualsToken),
node.ValuePrefix?.LiteralTokens);
var prefix = (MarkupTextLiteralSyntax)SyntaxFactory.MarkupTextLiteral(prefixTokens).Green.CreateRed(node, node.NamePrefix?.Position ?? node.Name.Position);
var name = node.Name.GetContent();
_builder.Push(new HtmlAttributeIntermediateNode()
{
AttributeName = node.Name.GetContent(),
Prefix = prefix.GetContent(),
Suffix = node.ValueSuffix?.GetContent() ?? string.Empty,
Source = BuildSourceSpanFromNode(node),
});
Visit(node.Value);
_builder.Pop();
}
public override void VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node)
{
var prefixTokens = MergeLiterals(node.NamePrefix?.LiteralTokens, node.Name.LiteralTokens);
var prefix = (MarkupTextLiteralSyntax)SyntaxFactory.MarkupTextLiteral(prefixTokens).Green.CreateRed(node, node.NamePrefix?.Position ?? node.Name.Position);
var name = node.Name.GetContent();
_builder.Add(new HtmlAttributeIntermediateNode()
{
AttributeName = node.Name.GetContent(),
Prefix = prefix.GetContent(),
Suffix = null,
Source = BuildSourceSpanFromNode(node),
});
}
// Example
// <input checked="hello-world `@false`"/>
// Prefix= (space)
// Children will contain a token for @false.
public override void VisitMarkupDynamicAttributeValue(MarkupDynamicAttributeValueSyntax node)
{
var containsExpression = false;
var descendantNodes = node.DescendantNodes(n =>
{
// Don't go into sub block. They may contain expressions but we only care about the top level.
return !(n.Parent is CSharpCodeBlockSyntax);
});
foreach (var child in descendantNodes)
{
if (child is CSharpImplicitExpressionSyntax || child is CSharpExplicitExpressionSyntax)
{
containsExpression = true;
}
}
if (containsExpression)
{
_builder.Push(new CSharpExpressionAttributeValueIntermediateNode()
{
Prefix = node.Prefix?.GetContent() ?? string.Empty,
Source = BuildSourceSpanFromNode(node),
});
}
else
{
_builder.Push(new CSharpCodeAttributeValueIntermediateNode()
{
Prefix = node.Prefix?.GetContent() ?? string.Empty,
Source = BuildSourceSpanFromNode(node),
});
}
Visit(node.Value);
_builder.Pop();
}
public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValueSyntax node)
{
_builder.Push(new HtmlAttributeValueIntermediateNode()
{
Prefix = node.Prefix?.GetContent() ?? string.Empty,
Source = BuildSourceSpanFromNode(node),
});
_builder.Add(new IntermediateToken()
{
Content = node.Value?.GetContent() ?? string.Empty,
Kind = TokenKind.Html,
Source = BuildSourceSpanFromNode(node.Value)
});
_builder.Pop();
}
public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node)
{
var context = node.GetSpanContext();
if (context != null && context.ChunkGenerator == SpanChunkGenerator.Null)
{
return;
}
if (node.LiteralTokens.Count == 1)
{
var token = node.LiteralTokens[0];
if (token != null &&
token.Kind == SyntaxKind.Marker &&
token.Content.Length == 0)
{
// We don't want to create IR nodes for marker tokens.
return;
}
}
// Combine chunks of HTML literal text if possible.
var source = BuildSourceSpanFromNode(node);
var currentChildren = _builder.Current.Children;
if (currentChildren.Count > 0 &&
currentChildren[currentChildren.Count - 1] is HtmlContentIntermediateNode existingHtmlContent)
{
if (existingHtmlContent.Source == null && source == null)
{
Combine(existingHtmlContent, node);
return;
}
if (source != null &&
existingHtmlContent.Source != null &&
existingHtmlContent.Source.Value.FilePath == source.Value.FilePath &&
existingHtmlContent.Source.Value.AbsoluteIndex + existingHtmlContent.Source.Value.Length == source.Value.AbsoluteIndex)
{
Combine(existingHtmlContent, node);
return;
}
}
_builder.Add(new HtmlContentIntermediateNode()
{
Source = source,
Children =
{
new IntermediateToken()
{
Content = node.GetContent(),
Kind = TokenKind.Html,
Source = source,
}
}
});
}
public override void VisitCSharpTemplateBlock(CSharpTemplateBlockSyntax node)
{
var templateNode = new TemplateIntermediateNode();
_builder.Push(templateNode);
base.VisitCSharpTemplateBlock(node);
_builder.Pop();
if (templateNode.Children.Count > 0)
{
var sourceRangeStart = templateNode
.Children
.FirstOrDefault(child => child.Source != null)
?.Source;
if (sourceRangeStart != null)
{
var contentLength = templateNode.Children.Sum(child => child.Source?.Length ?? 0);
templateNode.Source = new SourceSpan(
sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
sourceRangeStart.Value.AbsoluteIndex,
sourceRangeStart.Value.LineIndex,
sourceRangeStart.Value.CharacterIndex,
contentLength);
}
}
}
// CSharp expressions are broken up into blocks and spans because Razor allows Razor comments
// inside an expression.
// Ex:
// @DateTime.@*This is a comment*@Now
//
// We need to capture this in the IR so that we can give each piece the correct source mappings
public override void VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node)
{
if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode)
{
base.VisitCSharpExplicitExpression(node);
return;
}
var expressionNode = new CSharpExpressionIntermediateNode();
_builder.Push(expressionNode);
base.VisitCSharpExplicitExpression(node);
_builder.Pop();
if (expressionNode.Children.Count > 0)
{
var sourceRangeStart = expressionNode
.Children
.FirstOrDefault(child => child.Source != null)
?.Source;
if (sourceRangeStart != null)
{
var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0);
expressionNode.Source = new SourceSpan(
sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
sourceRangeStart.Value.AbsoluteIndex,
sourceRangeStart.Value.LineIndex,
sourceRangeStart.Value.CharacterIndex,
contentLength);
}
}
}
public override void VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node)
{
if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode)
{
base.VisitCSharpImplicitExpression(node);
return;
}
var expressionNode = new CSharpExpressionIntermediateNode();
_builder.Push(expressionNode);
base.VisitCSharpImplicitExpression(node);
_builder.Pop();
if (expressionNode.Children.Count > 0)
{
var sourceRangeStart = expressionNode
.Children
.FirstOrDefault(child => child.Source != null)
?.Source;
if (sourceRangeStart != null)
{
var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0);
expressionNode.Source = new SourceSpan(
sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
sourceRangeStart.Value.AbsoluteIndex,
sourceRangeStart.Value.LineIndex,
sourceRangeStart.Value.CharacterIndex,
contentLength);
}
}
}
public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax node)
{
if (_builder.Current is TagHelperHtmlAttributeIntermediateNode)
{
// If we are top level in a tag helper HTML attribute, we want to be rendered as markup.
// This case happens for duplicate non-string bound attributes. They would be initially be categorized as
// CSharp but since they are duplicate, they should just be markup.
var markupLiteral = SyntaxFactory.MarkupTextLiteral(node.LiteralTokens).Green.CreateRed(node.Parent, node.Position);
Visit(markupLiteral);
return;
}
_builder.Add(new IntermediateToken()
{
Content = node.GetContent(),
Kind = TokenKind.CSharp,
Source = BuildSourceSpanFromNode(node),
});
base.VisitCSharpExpressionLiteral(node);
}
public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
{
var context = node.GetSpanContext();
if (context == null || context.ChunkGenerator is StatementChunkGenerator)
{
var isAttributeValue = _builder.Current is CSharpCodeAttributeValueIntermediateNode;
if (!isAttributeValue)
{
var statementNode = new CSharpCodeIntermediateNode()
{
Source = BuildSourceSpanFromNode(node)
};
_builder.Push(statementNode);
}
_builder.Add(new IntermediateToken()
{
Content = node.GetContent(),
Kind = TokenKind.CSharp,
Source = BuildSourceSpanFromNode(node),
});
if (!isAttributeValue)
{
_builder.Pop();
}
}
base.VisitCSharpStatementLiteral(node);
}
public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node)
{
var info = node.TagHelperInfo;
var tagName = info.TagName;
var tagHelperNode = new TagHelperIntermediateNode()
{
TagName = tagName,
TagMode = info.TagMode,
Source = BuildSourceSpanFromNode(node)
};
foreach (var tagHelper in info.BindingResult.Descriptors)
{
tagHelperNode.TagHelpers.Add(tagHelper);
}
_builder.Push(tagHelperNode);
_builder.Push(new TagHelperBodyIntermediateNode());
foreach (var item in node.Body)
{
Visit(item);
}
_builder.Pop(); // Pop InitializeTagHelperStructureIntermediateNode
Visit(node.StartTag);
_builder.Pop(); // Pop TagHelperIntermediateNode
// No need to visit the end tag because we don't write any IR for it.
// We don't want to track attributes from a previous tag helper element.
_renderedBoundAttributeNames.Clear();
}
public override void VisitMarkupTagHelperStartTag(MarkupTagHelperStartTagSyntax node)
{
foreach (var child in node.Children)
{
if (child is MarkupTagHelperAttributeSyntax || child is MarkupMinimizedTagHelperAttributeSyntax)
{
Visit(child);
}
}
}
public override void VisitMarkupMinimizedTagHelperAttribute(MarkupMinimizedTagHelperAttributeSyntax node)
{
if (!_featureFlags.AllowMinimizedBooleanTagHelperAttributes)
{
// Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter
// has already logged an error if it was a non-boolean bound attribute; so we can skip.
return;
}
var element = node.FirstAncestorOrSelf<MarkupTagHelperElementSyntax>();
var descriptors = element.TagHelperInfo.BindingResult.Descriptors;
var attributeName = node.Name.GetContent();
var associatedDescriptors = descriptors.Where(descriptor =>
descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));
if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
{
foreach (var associatedDescriptor in associatedDescriptors)
{
var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
{
return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a);
});
var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attributeName);
if (!expectsBooleanValue)
{
// We do not allow minimized non-boolean bound attributes.
return;
}
var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
{
AttributeName = attributeName,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
Source = null,
IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
};
_builder.Add(setTagHelperProperty);
}
}
else
{
var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
{
AttributeName = attributeName,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
};
_builder.Add(addHtmlAttribute);
}
}
public override void VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
{
var element = node.FirstAncestorOrSelf<MarkupTagHelperElementSyntax>();
var descriptors = element.TagHelperInfo.BindingResult.Descriptors;
var attributeName = node.Name.GetContent();
var attributeValueNode = node.Value;
var associatedDescriptors = descriptors.Where(descriptor =>
descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));
if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
{
foreach (var associatedDescriptor in associatedDescriptors)
{
var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
{
return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a);
});
var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
{
AttributeName = attributeName,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
Source = BuildSourceSpanFromNode(attributeValueNode),
IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
};
_builder.Push(setTagHelperProperty);
VisitAttributeValue(attributeValueNode);
_builder.Pop();
}
}
else
{
var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
{
AttributeName = attributeName,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
};
_builder.Push(addHtmlAttribute);
VisitAttributeValue(attributeValueNode);
_builder.Pop();
}
}
private void VisitAttributeValue(SyntaxNode node)
{
if (node == null)
{
return;
}
IReadOnlyList<SyntaxNode> children = node.ChildNodes();
var position = node.Position;
if (children.First() is MarkupBlockSyntax markupBlock &&
markupBlock.Children.Count == 2 &&
markupBlock.Children[0] is MarkupTextLiteralSyntax &&
markupBlock.Children[1] is MarkupEphemeralTextLiteralSyntax)
{
// This is a special case when we have an attribute like attr="@@foo".
// In this case, we want the foo to be written out as HtmlContent and not HtmlAttributeValue.
Visit(markupBlock);
children = children.Skip(1).ToList();
position = children.Count > 0 ? children[0].Position : position;
}
if (children.All(c => c is MarkupLiteralAttributeValueSyntax))
{
var literalAttributeValueNodes = children.Cast<MarkupLiteralAttributeValueSyntax>().ToArray();
var valueTokens = SyntaxListBuilder<SyntaxToken>.Create();
for (var i = 0; i < literalAttributeValueNodes.Length; i++)
{
var mergedValue = MergeAttributeValue(literalAttributeValueNodes[i]);
valueTokens.AddRange(mergedValue.LiteralTokens);
}
var rewritten = SyntaxFactory.MarkupTextLiteral(valueTokens.ToList()).Green.CreateRed(node.Parent, position);
Visit(rewritten);
}
else if (children.All(c => c is MarkupTextLiteralSyntax))
{
var builder = SyntaxListBuilder<SyntaxToken>.Create();
var markupLiteralArray = children.Cast<MarkupTextLiteralSyntax>();
foreach (var literal in markupLiteralArray)
{
builder.AddRange(literal.LiteralTokens);
}
var rewritten = SyntaxFactory.MarkupTextLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
Visit(rewritten);
}
else if (children.All(c => c is CSharpExpressionLiteralSyntax))
{
var builder = SyntaxListBuilder<SyntaxToken>.Create();
var expressionLiteralArray = children.Cast<CSharpExpressionLiteralSyntax>();
SpanContext context = null;
foreach (var literal in expressionLiteralArray)
{
context = literal.GetSpanContext();
builder.AddRange(literal.LiteralTokens);
}
var rewritten = SyntaxFactory.CSharpExpressionLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
rewritten = context != null ? rewritten.WithSpanContext(context) : rewritten;
Visit(rewritten);
}
else
{
Visit(node);
}
}
private MarkupTextLiteralSyntax MergeAttributeValue(MarkupLiteralAttributeValueSyntax node)
{
var valueTokens = MergeLiterals(node.Prefix?.LiteralTokens, node.Value?.LiteralTokens);
var rewritten = node.Prefix?.Update(valueTokens) ?? node.Value?.Update(valueTokens);
rewritten = (MarkupTextLiteralSyntax)rewritten?.Green.CreateRed(node, node.Position);
var originalContext = rewritten.GetSpanContext();
if (originalContext != null)
{
rewritten = rewritten.WithSpanContext(new SpanContext(new MarkupChunkGenerator(), originalContext.EditHandler));
}
return rewritten;
}
private void Combine(HtmlContentIntermediateNode node, SyntaxNode item)
{
node.Children.Add(new IntermediateToken()
{
Content = item.GetContent(),
Kind = TokenKind.Html,
Source = BuildSourceSpanFromNode(item),
});
if (node.Source != null)
{
Debug.Assert(node.Source.Value.FilePath != null);
node.Source = new SourceSpan(
node.Source.Value.FilePath,
node.Source.Value.AbsoluteIndex,
node.Source.Value.LineIndex,
node.Source.Value.CharacterIndex,
node.Source.Value.Length + item.FullWidth);
}
}
private SyntaxList<SyntaxToken> MergeLiterals(params SyntaxList<SyntaxToken>?[] literals)
{
var builder = SyntaxListBuilder<SyntaxToken>.Create();
for (var i = 0; i < literals.Length; i++)
{
var literal = literals[i];
if (!literal.HasValue)
{
continue;
}
builder.AddRange(literal.Value);
}
return builder.ToList();
}
}
private class ImportsVisitor : LoweringVisitor
{
public ImportsVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, RazorParserFeatureFlags featureFlags)
@ -1099,18 +1786,6 @@ namespace Microsoft.AspNetCore.Razor.Language
}
}
private class DirectiveVisitor : IntermediateNodeWalker
{
public List<IntermediateNodeReference> Directives = new List<IntermediateNodeReference>();
public override void VisitDirective(DirectiveIntermediateNode node)
{
Directives.Add(new IntermediateNodeReference(Parent, node));
base.VisitDirective(node);
}
}
private static bool IsMalformed(IEnumerable<RazorDiagnostic> diagnostics)
=> diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
}

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
private bool _designTime;
public DefaultRazorParserOptionsBuilder(RazorConfiguration configuration)
public DefaultRazorParserOptionsBuilder(RazorConfiguration configuration, string fileKind)
{
if (configuration == null)
{
@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Razor.Language
Configuration = configuration;
LanguageVersion = configuration.LanguageVersion;
FileKind = fileKind;
}
public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version)
@ -34,6 +35,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public override ICollection<DirectiveDescriptor> Directives { get; } = new List<DirectiveDescriptor>();
public override string FileKind { get; }
public override bool ParseLeadingDirectives { get; set; }
public override RazorLanguageVersion LanguageVersion { get; }

View File

@ -15,9 +15,9 @@ namespace Microsoft.AspNetCore.Razor.Language
_configureOptions = ProjectEngine.EngineFeatures.OfType<IConfigureRazorParserOptionsFeature>().ToArray();
}
public RazorParserOptions Create(Action<RazorParserOptionsBuilder> configure)
public RazorParserOptions Create(string fileKind, Action<RazorParserOptionsBuilder> configure)
{
var builder = new DefaultRazorParserOptionsBuilder(ProjectEngine.Configuration);
var builder = new DefaultRazorParserOptionsBuilder(ProjectEngine.Configuration, fileKind);
configure?.Invoke(builder);
for (var i = 0; i < _configureOptions.Length; i++)

View File

@ -62,24 +62,55 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(projectItem));
}
var sourceDocument = RazorSourceDocument.ReadFrom(projectItem);
var importFeature = GetRequiredFeature<IImportProjectFeature>();
var importItems = importFeature.GetImports(projectItem);
var importSourceDocuments = GetImportSourceDocuments(importItems);
return CreateCodeDocumentCore(sourceDocument, projectItem.FileKind, importSourceDocuments, tagHelpers: null);
return CreateCodeDocumentCore(projectItem, configureParser: null, configureCodeGeneration: null);
}
internal override RazorCodeDocument CreateCodeDocumentCore(RazorSourceDocument sourceDocument, string fileKind, IReadOnlyList<RazorSourceDocument> importSourceDocuments, IReadOnlyList<TagHelperDescriptor> tagHelpers)
protected RazorCodeDocument CreateCodeDocumentCore(
RazorProjectItem projectItem,
Action<RazorParserOptionsBuilder> configureParser,
Action<RazorCodeGenerationOptionsBuilder> configureCodeGeneration)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var sourceDocument = RazorSourceDocument.ReadFrom(projectItem);
var importItems = new List<RazorProjectItem>();
var features = ProjectFeatures.OfType<IImportProjectFeature>();
foreach (var feature in features)
{
importItems.AddRange(feature.GetImports(projectItem));
}
var importSourceDocuments = GetImportSourceDocuments(importItems);
return CreateCodeDocumentCore(sourceDocument, projectItem.FileKind, importSourceDocuments, tagHelpers: null, configureParser, configureCodeGeneration);
}
protected RazorCodeDocument CreateCodeDocumentCore(
RazorSourceDocument sourceDocument,
string fileKind,
IReadOnlyList<RazorSourceDocument> importSourceDocuments,
IReadOnlyList<TagHelperDescriptor> tagHelpers,
Action<RazorParserOptionsBuilder> configureParser,
Action<RazorCodeGenerationOptionsBuilder> configureCodeGeneration)
{
if (sourceDocument == null)
{
throw new ArgumentNullException(nameof(sourceDocument));
}
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(ConfigureParserOptions);
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(ConfigureCodeGenerationOptions);
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(fileKind, builder =>
{
ConfigureParserOptions(builder);
configureParser?.Invoke(builder);
});
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(fileKind, builder =>
{
ConfigureCodeGenerationOptions(builder);
configureCodeGeneration?.Invoke(builder);
});
var codeDocument = RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
codeDocument.SetTagHelpers(tagHelpers);
@ -99,24 +130,55 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(projectItem));
}
var sourceDocument = RazorSourceDocument.ReadFrom(projectItem);
var importFeature = GetRequiredFeature<IImportProjectFeature>();
var importItems = importFeature.GetImports(projectItem);
var importSourceDocuments = GetImportSourceDocuments(importItems, suppressExceptions: true);
return CreateCodeDocumentDesignTimeCore(sourceDocument, projectItem.FileKind, importSourceDocuments, tagHelpers: null);
return CreateCodeDocumentDesignTimeCore(projectItem, configureParser: null, configureCodeGeneration: null);
}
internal override RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorSourceDocument sourceDocument, string fileKind, IReadOnlyList<RazorSourceDocument> importSourceDocuments, IReadOnlyList<TagHelperDescriptor> tagHelpers)
protected RazorCodeDocument CreateCodeDocumentDesignTimeCore(
RazorProjectItem projectItem,
Action<RazorParserOptionsBuilder> configureParser,
Action<RazorCodeGenerationOptionsBuilder> configureCodeGeneration)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var sourceDocument = RazorSourceDocument.ReadFrom(projectItem);
var importItems = new List<RazorProjectItem>();
var features = ProjectFeatures.OfType<IImportProjectFeature>();
foreach (var feature in features)
{
importItems.AddRange(feature.GetImports(projectItem));
}
var importSourceDocuments = GetImportSourceDocuments(importItems, suppressExceptions: true);
return CreateCodeDocumentDesignTimeCore(sourceDocument, projectItem.FileKind, importSourceDocuments, tagHelpers: null, configureParser, configureCodeGeneration);
}
protected RazorCodeDocument CreateCodeDocumentDesignTimeCore(
RazorSourceDocument sourceDocument,
string fileKind,
IReadOnlyList<RazorSourceDocument> importSourceDocuments,
IReadOnlyList<TagHelperDescriptor> tagHelpers,
Action<RazorParserOptionsBuilder> configureParser,
Action<RazorCodeGenerationOptionsBuilder> configureCodeGeneration)
{
if (sourceDocument == null)
{
throw new ArgumentNullException(nameof(sourceDocument));
}
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(ConfigureDesignTimeParserOptions);
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(ConfigureDesignTimeCodeGenerationOptions);
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(fileKind, builder =>
{
ConfigureDesignTimeParserOptions(builder);
configureParser?.Invoke(builder);
});
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(fileKind, builder =>
{
ConfigureDesignTimeCodeGenerationOptions(builder);
configureCodeGeneration?.Invoke(builder);
});
var codeDocument = RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
codeDocument.SetTagHelpers(tagHelpers);
@ -129,6 +191,62 @@ namespace Microsoft.AspNetCore.Razor.Language
return codeDocument;
}
public override RazorCodeDocument Process(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = CreateCodeDocumentCore(source, fileKind, importSources, tagHelpers, configureParser: null, configureCodeGeneration: null);
ProcessCore(codeDocument);
return codeDocument;
}
public override RazorCodeDocument ProcessDeclarationOnly(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var codeDocument = CreateCodeDocumentCore(projectItem, configureParser: null, configureCodeGeneration: (builder) =>
{
builder.SuppressPrimaryMethodBody = true;
});
ProcessCore(codeDocument);
return codeDocument;
}
public override RazorCodeDocument ProcessDeclarationOnly(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = CreateCodeDocumentCore(source, fileKind, importSources, tagHelpers, configureParser: null, configureCodeGeneration: (builder) =>
{
builder.SuppressPrimaryMethodBody = true;
});
ProcessCore(codeDocument);
return codeDocument;
}
public override RazorCodeDocument ProcessDesignTime(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = CreateCodeDocumentDesignTimeCore(source, fileKind, importSources, tagHelpers, configureParser: null, configureCodeGeneration: null);
ProcessCore(codeDocument);
return codeDocument;
}
protected override void ProcessCore(RazorCodeDocument codeDocument)
{
if (codeDocument == null)

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNetCore.Razor.Language.CodeGeneration;
@ -10,15 +11,14 @@ namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class DocumentClassifierPassBase : IntermediateNodePassBase, IRazorDocumentClassifierPass
{
private static readonly ICodeTargetExtension[] EmptyExtensionArray = new ICodeTargetExtension[0];
private ICodeTargetExtension[] _targetExtensions;
protected abstract string DocumentKind { get; }
protected IReadOnlyList<ICodeTargetExtension> TargetExtensions { get; private set; }
protected override void OnInitialized()
{
var feature = Engine.Features.OfType<IRazorTargetExtensionFeature>();
_targetExtensions = feature.FirstOrDefault()?.TargetExtensions.ToArray() ?? EmptyExtensionArray;
TargetExtensions = feature.FirstOrDefault()?.TargetExtensions.ToArray() ?? Array.Empty<ICodeTargetExtension>();
}
protected sealed override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
@ -35,6 +35,10 @@ namespace Microsoft.AspNetCore.Razor.Language
documentNode.DocumentKind = DocumentKind;
documentNode.Target = CreateTarget(codeDocument, documentNode.Options);
if (documentNode.Target == null)
{
throw new InvalidOperationException($"{nameof(CreateTarget)} must return a non-null {nameof(CodeTarget)}.");
}
Rewrite(codeDocument, documentNode);
}
@ -80,13 +84,14 @@ namespace Microsoft.AspNetCore.Razor.Language
protected abstract bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode);
private CodeTarget CreateTarget(RazorCodeDocument codeDocument, RazorCodeGenerationOptions options)
// virtual to allow replacing the code target wholesale.
protected virtual CodeTarget CreateTarget(RazorCodeDocument codeDocument, RazorCodeGenerationOptions options)
{
return CodeTarget.CreateDefault(codeDocument, options, (builder) =>
{
for (var i = 0; i < _targetExtensions.Length; i++)
for (var i = 0; i < TargetExtensions.Count; i++)
{
builder.TargetExtensions.Add(_targetExtensions[i]);
builder.TargetExtensions.Add(TargetExtensions[i]);
}
ConfigureTarget(builder);

View File

@ -2,9 +2,8 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language.Components
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
internal static class ImplementsDirective
{

View File

@ -3,11 +3,9 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
internal class ImplementsDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{

View File

@ -1,8 +1,10 @@
// 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.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
@ -26,6 +28,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
return;
}
if (string.Equals(documentNode.DocumentKind, ComponentDocumentClassifierPass.ComponentDocumentKind, StringComparison.Ordinal))
{
// Metadata attributes are not used for components.
return;
}
// We need to be able to compute the data we need for the [RazorCompiledItem] attribute - that includes
// a full type name, and a document kind, and optionally an identifier.
//

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.AspNetCore.Razor.Language
{
class FileKindDirectiveFeature
{
}
}

View File

@ -3,7 +3,7 @@
namespace Microsoft.AspNetCore.Razor.Language
{
internal static class FileKinds
public static class FileKinds
{
public static readonly string Component = "component";

View File

@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Razor.Language
{
internal interface IRazorCodeGenerationOptionsFactoryProjectFeature : IRazorProjectEngineFeature
{
RazorCodeGenerationOptions Create(Action<RazorCodeGenerationOptionsBuilder> configure);
RazorCodeGenerationOptions Create(string fileKind, Action<RazorCodeGenerationOptionsBuilder> configure);
}
}

View File

@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Razor.Language
{
internal interface IRazorParserOptionsFactoryProjectFeature : IRazorProjectEngineFeature
{
RazorParserOptions Create(Action<RazorParserOptionsBuilder> configure);
RazorParserOptions Create(string fileKind, Action<RazorParserOptionsBuilder> configure);
}
}

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
// An optional type inference node. This will be populated (and point to a different part of the tree)
// if this component call site requires type inference.
public ComponentTypeInferenceMethodIntermediateNode TypeInferenceNode { get; set; }
public string TypeName { get; set; }
public override void Accept(IntermediateNodeVisitor visitor)

View File

@ -154,6 +154,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
VisitDefault(node);
}
public virtual void VisitMarkupElement(MarkupElementIntermediateNode node)
{
VisitDefault(node);
}
public virtual void VisitMarkupBlock(MarkupBlockIntermediateNode node)
{
VisitDefault(node);
}
public virtual void VisitReferenceCapture(ReferenceCaptureIntermediateNode node)
{
VisitDefault(node);

View File

@ -2,14 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Components;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
internal class MarkupBlockIntermediateNode : ExtensionIntermediateNode
public sealed class MarkupBlockIntermediateNode : IntermediateNode
{
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
@ -22,25 +18,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
throw new ArgumentNullException(nameof(visitor));
}
AcceptExtensionNode<MarkupBlockIntermediateNode>(this, visitor);
visitor.VisitMarkupBlock(this);
}
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
public override void FormatNode(IntermediateNodeFormatter formatter)
{
if (target == null)
if (formatter == null)
{
throw new ArgumentNullException(nameof(target));
throw new ArgumentNullException(nameof(formatter));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var writer = (BlazorNodeWriter)context.NodeWriter;
writer.WriteHtmlBlock(context, this);
formatter.WriteContent(Content);
}
private string DebuggerDisplay => Content;
}
}

View File

@ -5,14 +5,10 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Components;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
internal class MarkupElementIntermediateNode : ExtensionIntermediateNode
public sealed class MarkupElementIntermediateNode : IntermediateNode
{
public IEnumerable<HtmlAttributeIntermediateNode> Attributes => Children.OfType<HtmlAttributeIntermediateNode>();
@ -36,56 +32,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
throw new ArgumentNullException(nameof(visitor));
}
AcceptExtensionNode<MarkupElementIntermediateNode>(this, visitor);
visitor.VisitMarkupElement(this);
}
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
public override void FormatNode(IntermediateNodeFormatter formatter)
{
if (target == null)
if (formatter == null)
{
throw new ArgumentNullException(nameof(target));
throw new ArgumentNullException(nameof(formatter));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
formatter.WriteContent(TagName);
var writer = (BlazorNodeWriter)context.NodeWriter;
writer.WriteHtmlElement(context, this);
}
private string DebuggerDisplay
{
get
{
var builder = new StringBuilder();
builder.Append("Element: ");
builder.Append("<");
builder.Append(TagName);
foreach (var attribute in Attributes)
{
builder.Append(" ");
builder.Append(attribute.AttributeName);
builder.Append("=\"...\"");
}
foreach (var capture in Captures)
{
builder.Append(" ");
builder.Append("ref");
builder.Append("=\"...\"");
}
builder.Append(">");
builder.Append(Body.Any() ? "..." : string.Empty);
builder.Append("</");
builder.Append(TagName);
builder.Append(">");
return builder.ToString();
}
formatter.WriteProperty(nameof(TagName), TagName);
}
}
}

View File

@ -7,7 +7,7 @@ using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language
{
public sealed class ItemCollection : ICollection<KeyValuePair<object, object>>
public sealed class ItemCollection : ICollection<KeyValuePair<object, object>>, ICollection
{
private readonly Dictionary<object, object> _inner;
@ -43,9 +43,9 @@ namespace Microsoft.AspNetCore.Razor.Language
public bool IsReadOnly => _inner != null;
int ICollection<KeyValuePair<object, object>>.Count => throw new NotImplementedException();
bool ICollection.IsSynchronized => ((ICollection)_inner).IsSynchronized;
bool ICollection<KeyValuePair<object, object>>.IsReadOnly => throw new NotImplementedException();
object ICollection.SyncRoot => ((ICollection)_inner).SyncRoot;
public void Add(KeyValuePair<object, object> item)
{
@ -120,5 +120,10 @@ namespace Microsoft.AspNetCore.Razor.Language
return ((ICollection<KeyValuePair<object, object>>)_inner).Remove(item);
}
void ICollection.CopyTo(Array array, int index)
{
((ICollection)_inner).CopyTo(array, index);
}
}
}

View File

@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public abstract bool DesignTime { get; }
public virtual string FileKind => null;
public abstract int IndentSize { get; set; }
public abstract bool IndentWithTabs { get; set; }

View File

@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
var allowMinimizedBooleanTagHelperAttributes = false;
var allowHtmlCommentsInTagHelpers = false;
var allowComponentFileKind = false;
var experimental_AllowConditionalDataDashAttributes = false;
if (version.CompareTo(RazorLanguageVersion.Version_2_1) >= 0)
@ -18,6 +19,12 @@ namespace Microsoft.AspNetCore.Razor.Language
allowHtmlCommentsInTagHelpers = true;
}
if (version.CompareTo(RazorLanguageVersion.Version_3_0) >= 0)
{
// Added in 3.0
allowComponentFileKind = true;
}
if (version.CompareTo(RazorLanguageVersion.Experimental) >= 0)
{
experimental_AllowConditionalDataDashAttributes = true;
@ -26,6 +33,7 @@ namespace Microsoft.AspNetCore.Razor.Language
return new DefaultRazorParserFeatureFlags(
allowMinimizedBooleanTagHelperAttributes,
allowHtmlCommentsInTagHelpers,
allowComponentFileKind,
experimental_AllowConditionalDataDashAttributes);
}
@ -33,6 +41,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public abstract bool AllowHtmlCommentsInTagHelpers { get; }
public abstract bool AllowComponentFileKind { get; }
public abstract bool EXPERIMENTAL_AllowConditionalDataDashAttributes { get; }
private class DefaultRazorParserFeatureFlags : RazorParserFeatureFlags
@ -40,10 +50,12 @@ namespace Microsoft.AspNetCore.Razor.Language
public DefaultRazorParserFeatureFlags(
bool allowMinimizedBooleanTagHelperAttributes,
bool allowHtmlCommentsInTagHelpers,
bool allowComponentFileKind,
bool experimental_AllowConditionalDataDashAttributes)
{
AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes;
AllowHtmlCommentsInTagHelpers = allowHtmlCommentsInTagHelpers;
AllowComponentFileKind = allowComponentFileKind;
EXPERIMENTAL_AllowConditionalDataDashAttributes = experimental_AllowConditionalDataDashAttributes;
}
@ -51,6 +63,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public override bool AllowHtmlCommentsInTagHelpers { get; }
public override bool AllowComponentFileKind { get; }
public override bool EXPERIMENTAL_AllowConditionalDataDashAttributes { get; }
}
}

View File

@ -13,6 +13,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public abstract ICollection<DirectiveDescriptor> Directives { get; }
public virtual string FileKind => null;
public abstract bool ParseLeadingDirectives { get; set; }
public virtual RazorLanguageVersion LanguageVersion { get; }

View File

@ -35,18 +35,26 @@ namespace Microsoft.AspNetCore.Razor.Language
return codeDocument;
}
internal virtual RazorCodeDocument Process(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
public virtual RazorCodeDocument Process(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = CreateCodeDocumentCore(source, fileKind, importSources, tagHelpers);
ProcessCore(codeDocument);
return codeDocument;
throw new NotImplementedException();
}
public virtual RazorCodeDocument ProcessDeclarationOnly(RazorProjectItem projectItem)
{
throw new NotImplementedException();
}
public virtual RazorCodeDocument ProcessDeclarationOnly(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
throw new NotImplementedException();
}
public virtual RazorCodeDocument ProcessDesignTime(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
throw new NotImplementedException();
}
public virtual RazorCodeDocument ProcessDesignTime(RazorProjectItem projectItem)
{
if (projectItem == null)
@ -59,42 +67,10 @@ namespace Microsoft.AspNetCore.Razor.Language
return codeDocument;
}
internal virtual RazorCodeDocument ProcessDesignTime(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = CreateCodeDocumentDesignTimeCore(source, fileKind, importSources, tagHelpers);
ProcessCore(codeDocument);
return codeDocument;
}
protected abstract RazorCodeDocument CreateCodeDocumentCore(RazorProjectItem projectItem);
internal virtual RazorCodeDocument CreateCodeDocumentCore(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
var codeDocument = RazorCodeDocument.Create(source, importSources);
if (fileKind != null)
{
codeDocument.SetFileKind(fileKind);
}
return codeDocument;
}
protected abstract RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorProjectItem projectItem);
internal virtual RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
var codeDocument = RazorCodeDocument.Create(source, importSources);
if (fileKind != null)
{
codeDocument.SetFileKind(fileKind);
}
return codeDocument;
}
protected abstract void ProcessCore(RazorCodeDocument codeDocument);
internal static RazorProjectEngine CreateEmpty(Action<RazorProjectEngineBuilder> configure = null)
@ -106,8 +82,6 @@ namespace Microsoft.AspNetCore.Razor.Language
return builder.Build();
}
internal static RazorProjectEngine Create() => Create(configure: null);
internal static RazorProjectEngine Create(Action<RazorProjectEngineBuilder> configure) => Create(RazorConfiguration.Default, RazorProjectFileSystem.Empty, configure);
public static RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem) => Create(configuration, fileSystem, configure: null);
@ -136,7 +110,16 @@ namespace Microsoft.AspNetCore.Razor.Language
// This allows extensions to rely on default features, and customizations to override choices made by
// extensions.
AddDefaultPhases(builder.Phases);
AddDefaultsFeatures(builder.Features);
AddDefaultFeatures(builder.Features);
if (configuration.LanguageVersion.CompareTo(RazorLanguageVersion.Version_3_0) >= 0)
{
FunctionsDirective.Register(builder);
ImplementsDirective.Register(builder);
InheritsDirective.Register(builder);
AddComponentFeatures(builder);
}
LoadExtensions(builder, configuration.Extensions);
@ -157,7 +140,7 @@ namespace Microsoft.AspNetCore.Razor.Language
phases.Add(new DefaultRazorCSharpLoweringPhase());
}
private static void AddDefaultsFeatures(ICollection<IRazorFeature> features)
private static void AddDefaultFeatures(ICollection<IRazorFeature> features)
{
features.Add(new DefaultImportProjectFeature());
@ -221,6 +204,36 @@ namespace Microsoft.AspNetCore.Razor.Language
});
}
private static void AddComponentFeatures(RazorProjectEngineBuilder builder)
{
// Project Engine Features
builder.Features.Add(new ComponentImportProjectFeature());
// Directives (conditional on file kind)
ComponentInjectDirective.Register(builder);
ComponentLayoutDirective.Register(builder);
ComponentPageDirective.Register(builder);
ComponentTypeParamDirective.Register(builder);
// Document Classifier
builder.Features.Add(new ComponentDocumentClassifierPass());
// Directive Classifier
builder.Features.Add(new ComponentWhitespacePass());
// Optimization
builder.Features.Add(new ComponentComplexAttributeContentPass());
builder.Features.Add(new ComponentLoweringPass());
builder.Features.Add(new ComponentScriptTagPass());
builder.Features.Add(new ComponentEventHandlerLoweringPass());
builder.Features.Add(new ComponentReferenceCaptureLoweringPass());
builder.Features.Add(new ComponentBindLoweringPass());
builder.Features.Add(new ComponentTemplateDiagnosticPass());
builder.Features.Add(new ComponentGenericTypePass());
builder.Features.Add(new ComponentChildContentDiagnosticPass());
builder.Features.Add(new ComponentHtmlBlockPass());
}
private static void LoadExtensions(RazorProjectEngineBuilder builder, IReadOnlyList<RazorExtension> extensions)
{
for (var i = 0; i < extensions.Count; i++)
@ -234,9 +247,6 @@ namespace Microsoft.AspNetCore.Razor.Language
initializer?.Initialize(builder);
}
}
// Default extensions.
ComponentExtensions.Register(builder);
}
}
}

View File

@ -145,6 +145,43 @@ namespace Microsoft.AspNetCore.Razor.Language
return builder;
}
/// <summary>
/// Adds the specified <see cref="DirectiveDescriptor"/> for the provided file kind.
/// </summary>
/// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
/// <param name="fileKind">The file kind, for which to register the directive. See <see cref="FileKinds"/>.</param>
/// <param name="directive">The <see cref="DirectiveDescriptor"/> to add.</param>
/// <returns>The <see cref="RazorProjectEngineBuilder"/>.</returns>
public static RazorProjectEngineBuilder AddDirective(this RazorProjectEngineBuilder builder, string fileKind, DirectiveDescriptor directive)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (fileKind == null)
{
throw new ArgumentNullException(nameof(fileKind));
}
if (directive == null)
{
throw new ArgumentNullException(nameof(directive));
}
var directiveFeature = GetDirectiveFeature(builder);
if (!directiveFeature.DirectivesByFileKind.TryGetValue(fileKind, out var directives))
{
directives = new List<DirectiveDescriptor>();
directiveFeature.DirectivesByFileKind.Add(fileKind, directives);
}
directives.Add(directive);
return builder;
}
/// <summary>
/// Adds the provided <see cref="RazorProjectItem" />s as imports to all project items processed
/// by the <see cref="RazorProjectEngine"/>.
@ -158,17 +195,15 @@ namespace Microsoft.AspNetCore.Razor.Language
{
throw new ArgumentNullException(nameof(builder));
}
var existingImportFeature = builder.Features.OfType<IImportProjectFeature>().First();
var testImportFeature = new AdditionalImportsProjectFeature(existingImportFeature, imports);
builder.SetImportFeature(testImportFeature);
builder.Features.Add(new AdditionalImportsProjectFeature(imports));
return builder;
}
private static IRazorDirectiveFeature GetDirectiveFeature(RazorProjectEngineBuilder builder)
private static DefaultRazorDirectiveFeature GetDirectiveFeature(RazorProjectEngineBuilder builder)
{
var directiveFeature = builder.Features.OfType<IRazorDirectiveFeature>().FirstOrDefault();
var directiveFeature = builder.Features.OfType<DefaultRazorDirectiveFeature>().FirstOrDefault();
if (directiveFeature == null)
{
directiveFeature = new DefaultRazorDirectiveFeature();
@ -204,31 +239,16 @@ namespace Microsoft.AspNetCore.Razor.Language
private class AdditionalImportsProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
{
private readonly IImportProjectFeature _existingImportFeature;
private readonly IEnumerable<RazorProjectItem> _imports;
private readonly IReadOnlyList<RazorProjectItem> _imports;
public override RazorProjectEngine ProjectEngine
public AdditionalImportsProjectFeature(params string[] imports)
{
get => base.ProjectEngine;
set
{
_existingImportFeature.ProjectEngine = value;
base.ProjectEngine = value;
}
}
public AdditionalImportsProjectFeature(IImportProjectFeature existingImportFeature, params string[] imports)
{
_existingImportFeature = existingImportFeature;
_imports = imports.Select(import => new InMemoryProjectItem(import));
_imports = imports.Select(import => new InMemoryProjectItem(import)).ToArray();
}
public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem)
{
var imports = _existingImportFeature.GetImports(projectItem).ToList();
imports.AddRange(_imports);
return imports;
return _imports;
}
private class InMemoryProjectItem : RazorProjectItem

View File

@ -248,7 +248,7 @@ namespace Microsoft.CodeAnalysis.Razor
var formatName = entry.Suffix == null ? "Format_" + entry.ValueAttribute : "Format_" + entry.Suffix;
var formatAttributeName = entry.Suffix == null ? "format-" + entry.ValueAttribute : "format-" + entry.Suffix;
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Bind.TagHelperKind, name, entry.Assembly);
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Bind.TagHelperKind, name, ComponentsApi.AssemblyName);
builder.Documentation = string.Format(
ComponentResources.BindTagHelper_Element_Documentation,
entry.ValueAttribute,

View File

@ -104,7 +104,7 @@ namespace Microsoft.CodeAnalysis.Razor
{
var entry = data[i];
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.EventHandler.TagHelperKind, entry.Attribute, entry.Assembly);
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.EventHandler.TagHelperKind, entry.Attribute, ComponentsApi.AssemblyName);
builder.Documentation = string.Format(
ComponentResources.EventHandlerTagHelper_Documentation,
entry.Attribute,

View File

@ -4,11 +4,13 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("rzls, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("rzc, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Language.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.LanguageServer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Test.Common, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LiveShare.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LiveShare.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -32,7 +32,9 @@ namespace Microsoft.CodeAnalysis.Razor
protected static TagHelperDescriptor[] ExcludeBuiltInComponents(TagHelperDescriptorProviderContext context)
{
return context.Results
.Where(c => c.AssemblyName == "TestAssembly")
.Where(c => c.GetTypeName() != "Microsoft.AspNetCore.Components.Bind")
.Where(c => c.GetTypeName() != "Microsoft.AspNetCore.Components.BindAttributes")
.Where(c => c.GetTypeName() != "Microsoft.AspNetCore.Components.EventHandlers")
.OrderBy(c => c.Name)
.ToArray();
}

View File

@ -222,7 +222,7 @@ namespace Test
bind.Documentation);
// These are all trivially derived from the assembly/namespace/type name
Assert.Equal("TestAssembly", bind.AssemblyName);
Assert.Equal("Microsoft.AspNetCore.Components", bind.AssemblyName);
Assert.Equal("Bind", bind.Name);
Assert.Equal("Test.BindAttributes", bind.DisplayName);
Assert.Equal("Test.BindAttributes", bind.GetTypeName());

View File

@ -62,7 +62,7 @@ namespace Test
item.Documentation);
// These are all trivially derived from the assembly/namespace/type name
Assert.Equal("TestAssembly", item.AssemblyName);
Assert.Equal("Microsoft.AspNetCore.Components", item.AssemblyName);
Assert.Equal("onclick", item.Name);
Assert.Equal("Test.EventHandlers", item.DisplayName);
Assert.Equal("Test.EventHandlers", item.GetTypeName());

View File

@ -0,0 +1,344 @@
// 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.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Components.RenderTree
{
/// <summary>
/// Represents an entry in a tree of user interface (UI) items.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public readonly struct RenderTreeFrame
{
// Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms,
// which means that all reference-type fields need to take up 8 bytes (except for the last
// one, which will be sized as either 4 or 8 bytes depending on the runtime platform).
// This is not optimal for the Mono-WebAssembly case because that's always 32-bit so the
// reference-type fields could be reduced to 4 bytes each. We could use ifdefs to have
// different fields offsets for the 32 and 64 bit compile targets, but then we'd have the
// complexity of needing different binaries when loaded into Mono-WASM vs desktop.
// Eventually we might stop using this shared memory interop altogether (and would have to
// if running as a web worker) so for now to keep things simple, treat reference types as
// 8 bytes here.
// --------------------------------------------------------------------------------
// Common
// --------------------------------------------------------------------------------
/// <summary>
/// Gets the sequence number of the frame. Sequence numbers indicate the relative source
/// positions of the instructions that inserted the frames. Sequence numbers are only
/// comparable within the same sequence (typically, the same source method).
/// </summary>
[FieldOffset(0)] public readonly int Sequence;
/// <summary>
/// Describes the type of this frame.
/// </summary>
[FieldOffset(4)] public readonly RenderTreeFrameType FrameType;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Element
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>
/// gets the number of frames in the subtree for which this frame is the root.
/// The value is zero if the frame has not yet been closed.
/// </summary>
[FieldOffset(8)] public readonly int ElementSubtreeLength;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>,
/// gets a name representing the type of the element. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string ElementName;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Text
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Text"/>,
/// gets the content of the text frame. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string TextContent;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Attribute
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>
/// gets the ID of the corresponding event handler, if any.
/// </summary>
[FieldOffset(8)] public readonly int AttributeEventHandlerId;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
/// gets the attribute name. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string AttributeName;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
/// gets the attribute value. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(24)] public readonly object AttributeValue;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Component
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>
/// gets the number of frames in the subtree for which this frame is the root.
/// The value is zero if the frame has not yet been closed.
/// </summary>
[FieldOffset(8)] public readonly int ComponentSubtreeLength;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component instance identifier.
/// </summary>
[FieldOffset(12)] public readonly int ComponentId;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the type of the child component.
/// </summary>
[FieldOffset(16)] public readonly Type ComponentType;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
/// gets the child component instance. Otherwise, the value is undefined.
/// </summary>
public IComponent Component => null;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Region
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Region"/>
/// gets the number of frames in the subtree for which this frame is the root.
/// The value is zero if the frame has not yet been closed.
/// </summary>
[FieldOffset(8)] public readonly int RegionSubtreeLength;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.ElementReferenceCapture
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
/// gets the ID of the reference capture. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string ElementReferenceCaptureId;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(24)] public readonly Action<ElementRef> ElementReferenceCaptureAction;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.ComponentReferenceCapture
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
/// gets the index of the parent frame representing the component being captured. Otherwise, the value is undefined.
/// WARNING: This index can only be used in the context of the frame's original render tree. If the frame is
/// copied elsewhere, such as to the ReferenceFrames buffer of a RenderTreeDiff, then the index will
/// not relate to entries in that other buffer.
/// Currently there's no scenario where this matters, but if there was, we could change all of the subtree
/// initialization logic in RenderTreeDiffBuilder to walk the frames hierarchically, then it would know
/// the parent index at the point where it wants to initialize the ComponentReferenceCapture frame.
/// </summary>
[FieldOffset(8)] public readonly int ComponentReferenceCaptureParentFrameIndex;
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly Action<object> ComponentReferenceCaptureAction;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.Markup
// --------------------------------------------------------------------------------
/// <summary>
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Markup"/>,
/// gets the content of the markup frame. Otherwise, the value is undefined.
/// </summary>
[FieldOffset(16)] public readonly string MarkupContent;
private RenderTreeFrame(int sequence, string elementName, int elementSubtreeLength)
: this()
{
FrameType = RenderTreeFrameType.Element;
Sequence = sequence;
ElementName = elementName;
ElementSubtreeLength = elementSubtreeLength;
}
private RenderTreeFrame(int sequence, Type componentType, int componentSubtreeLength)
: this()
{
FrameType = RenderTreeFrameType.Component;
Sequence = sequence;
ComponentType = componentType;
ComponentSubtreeLength = componentSubtreeLength;
}
private RenderTreeFrame(int sequence, string textContent)
: this()
{
FrameType = RenderTreeFrameType.Text;
Sequence = sequence;
TextContent = textContent;
}
private RenderTreeFrame(int sequence, string attributeName, object attributeValue)
: this()
{
FrameType = RenderTreeFrameType.Attribute;
Sequence = sequence;
AttributeName = attributeName;
AttributeValue = attributeValue;
}
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, int eventHandlerId)
: this()
{
FrameType = RenderTreeFrameType.Attribute;
Sequence = sequence;
AttributeName = attributeName;
AttributeValue = attributeValue;
AttributeEventHandlerId = eventHandlerId;
}
private RenderTreeFrame(int sequence, int regionSubtreeLength)
: this()
{
FrameType = RenderTreeFrameType.Region;
Sequence = sequence;
RegionSubtreeLength = regionSubtreeLength;
}
private RenderTreeFrame(int sequence, Action<ElementRef> elementReferenceCaptureAction, string elementReferenceCaptureId)
: this()
{
FrameType = RenderTreeFrameType.ElementReferenceCapture;
Sequence = sequence;
ElementReferenceCaptureAction = elementReferenceCaptureAction;
ElementReferenceCaptureId = elementReferenceCaptureId;
}
private RenderTreeFrame(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
: this()
{
FrameType = RenderTreeFrameType.ComponentReferenceCapture;
Sequence = sequence;
ComponentReferenceCaptureAction = componentReferenceCaptureAction;
ComponentReferenceCaptureParentFrameIndex = parentFrameIndex;
}
// If we need further constructors whose signatures clash with the patterns above,
// we can add extra args to this general-purpose one.
private RenderTreeFrame(int sequence, RenderTreeFrameType frameType, string markupContent)
: this()
{
FrameType = frameType;
Sequence = sequence;
MarkupContent = markupContent;
}
internal static RenderTreeFrame Element(int sequence, string elementName)
=> new RenderTreeFrame(sequence, elementName: elementName, elementSubtreeLength: 0);
internal static RenderTreeFrame Text(int sequence, string textContent)
=> new RenderTreeFrame(sequence, textContent: textContent);
internal static RenderTreeFrame Markup(int sequence, string markupContent)
=> new RenderTreeFrame(sequence, RenderTreeFrameType.Markup, markupContent);
internal static RenderTreeFrame Attribute(int sequence, string name, MulticastDelegate value)
=> new RenderTreeFrame(sequence, attributeName: name, attributeValue: value);
internal static RenderTreeFrame Attribute(int sequence, string name, object value)
=> new RenderTreeFrame(sequence, attributeName: name, attributeValue: value);
internal static RenderTreeFrame ChildComponent(int sequence, Type componentType)
=> new RenderTreeFrame(sequence, componentType, 0);
internal static RenderTreeFrame PlaceholderChildComponentWithSubtreeLength(int subtreeLength)
=> new RenderTreeFrame(0, typeof(IComponent), subtreeLength);
internal static RenderTreeFrame Region(int sequence)
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);
internal static RenderTreeFrame ElementReferenceCapture(int sequence, Action<ElementRef> elementReferenceCaptureAction)
=> new RenderTreeFrame(sequence, elementReferenceCaptureAction: elementReferenceCaptureAction, elementReferenceCaptureId: null);
internal static RenderTreeFrame ComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
=> new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex);
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
=> new RenderTreeFrame(Sequence, elementName: ElementName, elementSubtreeLength: elementSubtreeLength);
internal RenderTreeFrame WithComponentSubtreeLength(int componentSubtreeLength)
=> new RenderTreeFrame(Sequence, componentType: ComponentType, componentSubtreeLength: componentSubtreeLength);
internal RenderTreeFrame WithAttributeSequence(int sequence)
=> new RenderTreeFrame(sequence, attributeName: AttributeName, attributeValue: AttributeValue);
internal RenderTreeFrame WithAttributeEventHandlerId(int eventHandlerId)
=> new RenderTreeFrame(Sequence, AttributeName, AttributeValue, eventHandlerId);
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
internal RenderTreeFrame WithElementReferenceCaptureId(string elementReferenceCaptureId)
=> new RenderTreeFrame(Sequence, ElementReferenceCaptureAction, elementReferenceCaptureId);
/// <inheritdoc />
// Just to be nice for debugging and unit tests.
public override string ToString()
{
switch (FrameType)
{
case RenderTreeFrameType.Attribute:
return $"Attribute: (seq={Sequence}, id={AttributeEventHandlerId}) '{AttributeName}'='{AttributeValue}'";
case RenderTreeFrameType.Component:
return $"Component: (seq={Sequence}, len={ComponentSubtreeLength}) {ComponentType}";
case RenderTreeFrameType.Element:
return $"Element: (seq={Sequence}, len={ElementSubtreeLength}) {ElementName}";
case RenderTreeFrameType.Region:
return $"Region: (seq={Sequence}, len={RegionSubtreeLength})";
case RenderTreeFrameType.Text:
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
case RenderTreeFrameType.Markup:
return $"Markup: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
case RenderTreeFrameType.ElementReferenceCapture:
return $"ElementReferenceCapture: (seq={Sequence}, len=n/a) {ElementReferenceCaptureAction}";
}
return base.ToString();
}
private static string EscapeNewlines(string text)
{
return text.Replace("\n", "\\n").Replace("\r\n", "\\r\\n");
}
}
}

View File

@ -0,0 +1,54 @@
// 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
{
/// <summary>
/// Describes the type of a <see cref="RenderTreeFrame"/>.
/// </summary>
public enum RenderTreeFrameType : int
{
/// <summary>
/// Represents a container for other frames.
/// </summary>
Element = 1,
/// <summary>
/// Represents text content.
/// </summary>
Text = 2,
/// <summary>
/// Represents a key-value pair associated with another <see cref="RenderTreeFrame"/>.
/// </summary>
Attribute = 3,
/// <summary>
/// Represents a child component.
/// </summary>
Component = 4,
/// <summary>
/// Defines the boundary around range of sibling frames that should be treated as an
/// unsplittable group for the purposes of diffing. This is typically used when appending
/// a tree fragment generated by external code, because the sequence numbers in that tree
/// fragment are not comparable to sequence numbers outside it.
/// </summary>
Region = 5,
/// <summary>
/// Represents an instruction to capture or update a reference to the parent element.
/// </summary>
ElementReferenceCapture = 6,
/// <summary>
/// Represents an instruction to capture or update a reference to the parent component.
/// </summary>
ComponentReferenceCapture = 7,
/// <summary>
/// Represents a block of markup content.
/// </summary>
Markup = 8,
}
}

View File

@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
return projectItem;
}
protected RazorProjectItem CreateProjectItemFromFile(string filePath = null)
protected RazorProjectItem CreateProjectItemFromFile(string filePath = null, string fileKind = null)
{
if (FileName == null)
{
@ -201,7 +201,8 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
basePath: workingDirectory,
filePath: filePath,
physicalPath: fullPath,
relativePhysicalPath: sourceFileName)
relativePhysicalPath: sourceFileName,
fileKind: fileKind)
{
Content = fileContent,
};
@ -321,10 +322,14 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
});
}
// Decorate the import feature so we can normalize line endings.
var importFeature = b.Features.OfType<IImportProjectFeature>().FirstOrDefault();
b.Features.Add(new NormalizedDefaultImportFeature(importFeature, LineEnding));
b.Features.Remove(importFeature);
b.Features.Add(new DefaultTypeNameFeature());
// Decorate each import feature so we can normalize line endings.
foreach (var feature in b.Features.OfType<IImportProjectFeature>().ToArray())
{
b.Features.Remove(feature);
b.Features.Add(new NormalizedDefaultImportFeature(feature, LineEnding));
}
});
}

Some files were not shown because too many files have changed in this diff Show More