Remove Blazor Compiler
This commit is contained in:
parent
9e89a69a78
commit
bdb5982dbd
|
|
@ -36,7 +36,6 @@ This can be done once #4246 is complete, and done in conjunction with converting
|
|||
<PackageArtifact Include="Microsoft.AspNetCore.Components.Analyzers" Category="ship" />
|
||||
<PackageArtifact Include="Microsoft.AspNetCore.Components.Browser" Category="ship" />
|
||||
<PackageArtifact Include="Microsoft.AspNetCore.Components.Build" Category="ship" />
|
||||
<PackageArtifact Include="Microsoft.AspNetCore.Components.Razor.Extensions" Category="ship" />
|
||||
<PackageArtifact Include="Microsoft.AspNetCore.Components.Server" Category="ship" />
|
||||
<PackageArtifact Include="Microsoft.AspNetCore.Components" Category="ship" />
|
||||
<PackageArtifact Include="Microsoft.AspNetCore.Connections.Abstractions" Category="ship" />
|
||||
|
|
|
|||
|
|
@ -1,24 +1,55 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28306.52
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28414.68
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B867E038-B3CE-43E3-9292-61568C46CDEB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HostedInAspNet", "HostedInAspNet", "{4D367450-96E9-4C8C-8B56-EED8ADE3A20D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MonoSanity", "MonoSanity", "{2A076721-6081-4517-8329-B9E5110D6DAC}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testapps", "testapps", "{4AE0D35B-D97A-44D0-8392-C9240377DCCE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BlazorHosted-CSharp", "BlazorHosted-CSharp", "{73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tooling", "tooling", "{F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{36A7DEB7-5F88-4BFB-B57E-79EEC9950E25}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ComponentsApp", "ComponentsApp", "{3173A9C0-4F66-4064-83EC-3C206F1430FB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "blazor", "blazor", "{E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{DDE4B710-6936-4E17-9CA0-54C45333ED15}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C7B9207E-BF85-422D-9EBC-E243C399F619}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AD41FFD8-2C0C-4F0C-9537-25C2A3A7A1F4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{A63F9FCD-29D7-4D49-BC70-7E0F57D0BA60}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\shared\ComponentsApi.cs = src\shared\ComponentsApi.cs
|
||||
src\shared\ConventionBasedStartup.cs = src\shared\ConventionBasedStartup.cs
|
||||
src\shared\IBlazorStartup.cs = src\shared\IBlazorStartup.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{32B33872-B599-4913-9F90-EDB5F9E24B18}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanity", "blazor\samples\MonoSanity\MonoSanity.csproj", "{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Server", "src\Microsoft.AspNetCore.Components.Server\Microsoft.AspNetCore.Components.Server.csproj", "{5A694793-3257-4D37-BB74-4A41B3894685}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.E2ETest", "test\Microsoft.AspNetCore.Components.E2ETest\Microsoft.AspNetCore.Components.E2ETest.csproj", "{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoSanityClient", "blazor\samples\MonoSanityClient\MonoSanityClient.csproj", "{06AAAE9E-96DE-4574-97DA-9C4C7D9FE990}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HostedInAspNet", "HostedInAspNet", "{4D367450-96E9-4C8C-8B56-EED8ADE3A20D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "blazor\samples\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{B4335F7C-4E86-4559-821F-F1B1C75F5FAE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Server", "blazor\samples\HostedInAspNet.Server\HostedInAspNet.Server.csproj", "{F8996835-41F7-4663-91DF-3B5652ADC37D}"
|
||||
|
|
@ -27,8 +58,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneApp", "blazor\samples\StandaloneApp\StandaloneApp.csproj", "{B241434A-1642-44CC-AE9A-2012B5C5BD02}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MonoSanity", "MonoSanity", "{2A076721-6081-4517-8329-B9E5110D6DAC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Build.Test", "test\Microsoft.AspNetCore.Components.Build.Test\Microsoft.AspNetCore.Components.Build.Test.csproj", "{709C7EBE-EB93-4F6D-9491-D714B0D2E898}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Build", "src\Microsoft.AspNetCore.Components.Build\Microsoft.AspNetCore.Components.Build.csproj", "{8B3D0F1C-0E38-4E6D-BFF1-C4FDA0CD9815}"
|
||||
|
|
@ -37,34 +66,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Test", "test\Microsoft.AspNetCore.Components.Test\Microsoft.AspNetCore.Components.Test.csproj", "{8FD8636E-AFA5-434D-8857-06D02686741A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testapps", "testapps", "{4AE0D35B-D97A-44D0-8392-C9240377DCCE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicTestApp", "test\testapps\BasicTestApp\BasicTestApp.csproj", "{2838CB6F-D2C7-4C0A-A994-C72E56F16984}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Razor.Extensions", "src\Microsoft.AspNetCore.Components.Razor.Extensions\Microsoft.AspNetCore.Components.Razor.Extensions.csproj", "{D652A019-B765-4922-B7B8-3AB1C58338D7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestServer", "test\testapps\TestServer\TestServer.csproj", "{29CD3FC6-49E3-4756-B5DF-E03B46E5CD45}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Cli", "blazor\src\Microsoft.AspNetCore.Blazor.Cli\Microsoft.AspNetCore.Blazor.Cli.csproj", "{AF79BB84-BAE6-4F9A-9AD5-B0E3D7455288}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Templates", "blazor\src\Microsoft.AspNetCore.Blazor.Templates\Microsoft.AspNetCore.Blazor.Templates.csproj", "{8C160273-0A1D-4D79-9F7B-7687B2D2F7C4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BlazorHosted-CSharp", "BlazorHosted-CSharp", "{73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Razor.Extensions.Test", "test\Microsoft.AspNetCore.Components.Razor.Extensions.Test\Microsoft.AspNetCore.Components.Razor.Extensions.Test.csproj", "{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tooling", "tooling", "{F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.LanguageServices.Blazor", "blazor\tooling\Microsoft.VisualStudio.LanguageServices.Blazor\Microsoft.VisualStudio.LanguageServices.Blazor.csproj", "{43E39257-7DC1-46BD-9BD9-2319A1313D07}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.BlazorExtension", "blazor\tooling\Microsoft.VisualStudio.BlazorExtension\Microsoft.VisualStudio.BlazorExtension.csproj", "{9088E4E4-B855-457F-AE9E-D86709A5E1F4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestContentPackage", "test\testapps\TestContentPackage\TestContentPackage.csproj", "{C57382BC-EE93-49D5-BC40-5C98AF8AA048}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{36A7DEB7-5F88-4BFB-B57E-79EEC9950E25}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Performance", "benchmarks\Microsoft.AspNetCore.Components.Performance\Microsoft.AspNetCore.Components.Performance.csproj", "{50F6820F-D058-4E68-9E15-801F893F514E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Analyzers", "src\Microsoft.AspNetCore.Components.Analyzers\Microsoft.AspNetCore.Components.Analyzers.csproj", "{6DDD6A29-0A3E-417F-976C-5FE3FDA74055}"
|
||||
|
|
@ -73,24 +88,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Server.Test", "test\Microsoft.AspNetCore.Components.Server.Test\Microsoft.AspNetCore.Components.Server.Test.csproj", "{142AA6BC-5110-486B-A34D-6878E5E2CE95}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ComponentsApp", "ComponentsApp", "{3173A9C0-4F66-4064-83EC-3C206F1430FB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "samples\ComponentsApp.Server\ComponentsApp.Server.csproj", "{5655AFF9-612C-4947-8221-7DB6949A6CA4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.App", "samples\ComponentsApp.App\ComponentsApp.App.csproj", "{33C361D8-CAF1-47C0-A344-251AEF4FE1FE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.E2EPerformance", "blazor\benchmarks\Microsoft.AspNetCore.Blazor.E2EPerformance\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj", "{CCEC81C4-1A3C-40DC-952F-074712C46180}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.TagHelperWorkaround", "src\Microsoft.AspNetCore.Components.TagHelperWorkaround\Microsoft.AspNetCore.Components.TagHelperWorkaround.csproj", "{F71D78AB-A07E-415D-BF3F-1B9991628214}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "blazor", "blazor", "{E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{DDE4B710-6936-4E17-9CA0-54C45333ED15}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C7B9207E-BF85-422D-9EBC-E243C399F619}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AD41FFD8-2C0C-4F0C-9537-25C2A3A7A1F4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorLibrary-CSharp", "blazor\src\Microsoft.AspNetCore.Blazor.Templates\content\BlazorLibrary-CSharp\BlazorLibrary-CSharp.csproj", "{8C5D8A27-E6E6-4E5B-9389-206B5519658C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorHosted-CSharp.Client", "blazor\src\Microsoft.AspNetCore.Blazor.Templates\content\BlazorHosted-CSharp\BlazorHosted-CSharp.Client\BlazorHosted-CSharp.Client.csproj", "{61150807-8DBD-4456-9C6E-D97268181C2B}"
|
||||
|
|
@ -101,16 +104,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorHosted-CSharp.Shared"
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorStandalone-CSharp", "blazor\src\Microsoft.AspNetCore.Blazor.Templates\content\BlazorStandalone-CSharp\BlazorStandalone-CSharp.csproj", "{2F92DE29-78E9-4A04-8F4F-F8F40E02FE98}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Blazor", "blazor\src\Microsoft.AspNetCore.Blazor\Microsoft.AspNetCore.Blazor.csproj", "{A42AC30B-45E3-4907-99A9-9ABDA45973D7}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{A63F9FCD-29D7-4D49-BC70-7E0F57D0BA60}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\shared\ComponentsApi.cs = src\shared\ComponentsApi.cs
|
||||
src\shared\ConventionBasedStartup.cs = src\shared\ConventionBasedStartup.cs
|
||||
src\shared\IBlazorStartup.cs = src\shared\IBlazorStartup.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{32B33872-B599-4913-9F90-EDB5F9E24B18}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor", "blazor\src\Microsoft.AspNetCore.Blazor\Microsoft.AspNetCore.Blazor.csproj", "{A42AC30B-45E3-4907-99A9-9ABDA45973D7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Test", "blazor\test\Microsoft.AspNetCore.Blazor.Test\Microsoft.AspNetCore.Blazor.Test.csproj", "{85813607-297F-4D39-92F7-89379FD80D70}"
|
||||
EndProject
|
||||
|
|
@ -228,14 +222,6 @@ Global
|
|||
{2838CB6F-D2C7-4C0A-A994-C72E56F16984}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2838CB6F-D2C7-4C0A-A994-C72E56F16984}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2838CB6F-D2C7-4C0A-A994-C72E56F16984}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{29CD3FC6-49E3-4756-B5DF-E03B46E5CD45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{29CD3FC6-49E3-4756-B5DF-E03B46E5CD45}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{29CD3FC6-49E3-4756-B5DF-E03B46E5CD45}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -260,14 +246,6 @@ Global
|
|||
{8C160273-0A1D-4D79-9F7B-7687B2D2F7C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C160273-0A1D-4D79-9F7B-7687B2D2F7C4}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C160273-0A1D-4D79-9F7B-7687B2D2F7C4}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{43E39257-7DC1-46BD-9BD9-2319A1313D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{43E39257-7DC1-46BD-9BD9-2319A1313D07}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{43E39257-7DC1-46BD-9BD9-2319A1313D07}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -346,14 +324,6 @@ Global
|
|||
{CCEC81C4-1A3C-40DC-952F-074712C46180}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CCEC81C4-1A3C-40DC-952F-074712C46180}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CCEC81C4-1A3C-40DC-952F-074712C46180}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C5D8A27-E6E6-4E5B-9389-206B5519658C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C5D8A27-E6E6-4E5B-9389-206B5519658C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C5D8A27-E6E6-4E5B-9389-206B5519658C}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -423,30 +393,34 @@ Global
|
|||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{4D367450-96E9-4C8C-8B56-EED8ADE3A20D} = {C7B9207E-BF85-422D-9EBC-E243C399F619}
|
||||
{2A076721-6081-4517-8329-B9E5110D6DAC} = {C7B9207E-BF85-422D-9EBC-E243C399F619}
|
||||
{4AE0D35B-D97A-44D0-8392-C9240377DCCE} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{E8EBA72C-D555-43AE-BC98-F0B2D05F6A07} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8} = {E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}
|
||||
{F563ABB6-85FB-4CFC-B0D2-1D5130E8246D} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{3173A9C0-4F66-4064-83EC-3C206F1430FB} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}
|
||||
{DDE4B710-6936-4E17-9CA0-54C45333ED15} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{C7B9207E-BF85-422D-9EBC-E243C399F619} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{AD41FFD8-2C0C-4F0C-9537-25C2A3A7A1F4} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{A63F9FCD-29D7-4D49-BC70-7E0F57D0BA60} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{32B33872-B599-4913-9F90-EDB5F9E24B18} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{7C53BB6B-5906-4753-B507-C9FCC2F7E5B7} = {2A076721-6081-4517-8329-B9E5110D6DAC}
|
||||
{5A694793-3257-4D37-BB74-4A41B3894685} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{5BC2A10D-B6CA-43AE-B73C-2A41AE1039F9} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{06AAAE9E-96DE-4574-97DA-9C4C7D9FE990} = {2A076721-6081-4517-8329-B9E5110D6DAC}
|
||||
{4D367450-96E9-4C8C-8B56-EED8ADE3A20D} = {C7B9207E-BF85-422D-9EBC-E243C399F619}
|
||||
{B4335F7C-4E86-4559-821F-F1B1C75F5FAE} = {4D367450-96E9-4C8C-8B56-EED8ADE3A20D}
|
||||
{F8996835-41F7-4663-91DF-3B5652ADC37D} = {4D367450-96E9-4C8C-8B56-EED8ADE3A20D}
|
||||
{7FD8C650-74B3-4153-AEA1-00F4F6AF393D} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{B241434A-1642-44CC-AE9A-2012B5C5BD02} = {C7B9207E-BF85-422D-9EBC-E243C399F619}
|
||||
{2A076721-6081-4517-8329-B9E5110D6DAC} = {C7B9207E-BF85-422D-9EBC-E243C399F619}
|
||||
{709C7EBE-EB93-4F6D-9491-D714B0D2E898} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{8B3D0F1C-0E38-4E6D-BFF1-C4FDA0CD9815} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{8A19B1CE-9B62-4440-93B3-152DDBB39D0A} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{8FD8636E-AFA5-434D-8857-06D02686741A} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{4AE0D35B-D97A-44D0-8392-C9240377DCCE} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{2838CB6F-D2C7-4C0A-A994-C72E56F16984} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
|
||||
{D652A019-B765-4922-B7B8-3AB1C58338D7} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{29CD3FC6-49E3-4756-B5DF-E03B46E5CD45} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
|
||||
{AF79BB84-BAE6-4F9A-9AD5-B0E3D7455288} = {AD41FFD8-2C0C-4F0C-9537-25C2A3A7A1F4}
|
||||
{8C160273-0A1D-4D79-9F7B-7687B2D2F7C4} = {AD41FFD8-2C0C-4F0C-9537-25C2A3A7A1F4}
|
||||
{E8EBA72C-D555-43AE-BC98-F0B2D05F6A07} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8} = {E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{F563ABB6-85FB-4CFC-B0D2-1D5130E8246D} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{43E39257-7DC1-46BD-9BD9-2319A1313D07} = {F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}
|
||||
{9088E4E4-B855-457F-AE9E-D86709A5E1F4} = {F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}
|
||||
{C57382BC-EE93-49D5-BC40-5C98AF8AA048} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
|
||||
|
|
@ -454,22 +428,15 @@ Global
|
|||
{6DDD6A29-0A3E-417F-976C-5FE3FDA74055} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{CF3B5990-7A05-4993-AACA-D2C8D7AFF6E6} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{142AA6BC-5110-486B-A34D-6878E5E2CE95} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{3173A9C0-4F66-4064-83EC-3C206F1430FB} = {F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}
|
||||
{5655AFF9-612C-4947-8221-7DB6949A6CA4} = {3173A9C0-4F66-4064-83EC-3C206F1430FB}
|
||||
{33C361D8-CAF1-47C0-A344-251AEF4FE1FE} = {3173A9C0-4F66-4064-83EC-3C206F1430FB}
|
||||
{CCEC81C4-1A3C-40DC-952F-074712C46180} = {DDE4B710-6936-4E17-9CA0-54C45333ED15}
|
||||
{F71D78AB-A07E-415D-BF3F-1B9991628214} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{DDE4B710-6936-4E17-9CA0-54C45333ED15} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{C7B9207E-BF85-422D-9EBC-E243C399F619} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{AD41FFD8-2C0C-4F0C-9537-25C2A3A7A1F4} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{8C5D8A27-E6E6-4E5B-9389-206B5519658C} = {E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}
|
||||
{61150807-8DBD-4456-9C6E-D97268181C2B} = {73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}
|
||||
{EB8B8216-BD47-4635-A23D-21135943F2B7} = {73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}
|
||||
{46B3D506-E351-4668-AC65-0719191B06A7} = {73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}
|
||||
{2F92DE29-78E9-4A04-8F4F-F8F40E02FE98} = {E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}
|
||||
{A42AC30B-45E3-4907-99A9-9ABDA45973D7} = {AD41FFD8-2C0C-4F0C-9537-25C2A3A7A1F4}
|
||||
{A63F9FCD-29D7-4D49-BC70-7E0F57D0BA60} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{32B33872-B599-4913-9F90-EDB5F9E24B18} = {E5DDF29D-DFC9-43E3-AC3A-5B6D54B786D5}
|
||||
{85813607-297F-4D39-92F7-89379FD80D70} = {32B33872-B599-4913-9F90-EDB5F9E24B18}
|
||||
{E52F5005-26EA-4764-8ECF-41D324AAA6D9} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
EndGlobalSection
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion>15.0</MinimumVisualStudioVersion>
|
||||
<MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<UseCodebase>true</UseCodebase>
|
||||
<!-- Don't import the directory props and targets, they aren't compatible with an old-style csproj -->
|
||||
|
|
@ -130,20 +130,6 @@
|
|||
<IncludeOutputGroupsInVSIXLocalOnly>
|
||||
</IncludeOutputGroupsInVSIXLocalOnly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Components.Razor.Extensions\Microsoft.AspNetCore.Components.Razor.Extensions.csproj">
|
||||
<Name>Microsoft.AspNetCore.Components.Razor.Extensions</Name>
|
||||
<Private>False</Private>
|
||||
<IncludeOutputGroupsInVSIX>
|
||||
</IncludeOutputGroupsInVSIX>
|
||||
<IncludeOutputGroupsInVSIXLocalOnly>
|
||||
</IncludeOutputGroupsInVSIXLocalOnly>
|
||||
</ProjectReference>
|
||||
<Content Include="..\..\..\src\Microsoft.AspNetCore.Components.Razor.Extensions\bin\$(Configuration)\net461\Microsoft.AspNetCore.Components.Razor.Extensions.dll">
|
||||
<Link>Microsoft.AspNetCore.Components.Razor.Extensions.dll</Link>
|
||||
<IncludeInVSIX>true</IncludeInVSIX>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
<ProjectReference Include="..\Microsoft.VisualStudio.LanguageServices.Blazor\Microsoft.VisualStudio.LanguageServices.Blazor.csproj">
|
||||
<Project>{b9f7f502-6dd2-4e77-8fd1-cbd76f695b26}</Project>
|
||||
<Name>Microsoft.VisualStudio.LanguageServices.Blazor</Name>
|
||||
|
|
@ -159,16 +145,6 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
<!--
|
||||
This is built as a P2P by Microsoft.VisualStudio.LanguageServices.Blazor. This is required, adding the P2P
|
||||
to this project will cause a NU1201 error that doesn't have a workaround.
|
||||
-->
|
||||
<Content Include="$(TargetDir)AngleSharp.dll">
|
||||
<Link>AngleSharp.dll</Link>
|
||||
<IncludeInVSIX>true</IncludeInVSIX>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<!--
|
||||
We need to generate the assembly attributes for our assembly using the version from the build, so
|
||||
|
|
|
|||
|
|
@ -8,23 +8,10 @@ using Microsoft.VisualStudio.Shell;
|
|||
//
|
||||
// The versions here need to match what the build is producing. If you change the version numbers
|
||||
// for the referenced assemblies, this needs to change as well.
|
||||
[assembly: ProvideBindingRedirection(
|
||||
AssemblyName = "Microsoft.AspNetCore.Components.Razor.Extensions",
|
||||
GenerateCodeBase = true,
|
||||
PublicKeyToken = "",
|
||||
OldVersionLowerBound = "0.0.0.0",
|
||||
OldVersionUpperBound = "3.0.0.0",
|
||||
NewVersion = "3.0.0.0")]
|
||||
[assembly: ProvideBindingRedirection(
|
||||
AssemblyName = "Microsoft.VisualStudio.LanguageServices.Blazor",
|
||||
GenerateCodeBase = true,
|
||||
PublicKeyToken = "",
|
||||
OldVersionLowerBound = "0.0.0.0",
|
||||
OldVersionUpperBound = "0.8.0.0",
|
||||
NewVersion = "0.8.0.0")]
|
||||
[assembly: ProvideBindingRedirection(
|
||||
AssemblyName = "AngleSharp",
|
||||
PublicKeyToken = "",
|
||||
OldVersionLowerBound = "0.0.0.0",
|
||||
OldVersionUpperBound = "0.9.9.0",
|
||||
NewVersion = "0.9.9.0")]
|
||||
NewVersion = "0.8.0.0")]
|
||||
|
|
@ -20,8 +20,6 @@
|
|||
<Assets>
|
||||
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
|
||||
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="File" Path="Microsoft.VisualStudio.LanguageServices.Blazor.dll" />
|
||||
<Asset Type="Microsoft.VisualStudio.Assembly" d:Source="File" Path="AngleSharp.dll" />
|
||||
<Asset Type="Microsoft.VisualStudio.Assembly" d:Source="File" Path="Microsoft.AspNetCore.Components.Razor.Extensions.dll" />
|
||||
<Asset Type="Microsoft.VisualStudio.Assembly" d:Source="File" Path="Microsoft.VisualStudio.LanguageServices.Blazor.dll" />
|
||||
<Asset Type="Microsoft.VisualStudio.VsPackage" Path="Templates.pkgdef" />
|
||||
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="File" Path="CodeSnippets.pkgdef" />
|
||||
|
|
|
|||
|
|
@ -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 System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Blazor
|
||||
{
|
||||
// This factory is wired up based on a match of the $(DefaultRazorConfiguration) property is MSBuild
|
||||
// keep this in sync with the supported runtime/build version.
|
||||
[ExportCustomProjectEngineFactory("Blazor-0.1", SupportsSerialization = false)]
|
||||
internal class BlazorProjectEngineFactory : IProjectEngineFactory
|
||||
{
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, b =>
|
||||
{
|
||||
configure?.Invoke(b);
|
||||
new BlazorExtensionInitializer().Initialize(b);
|
||||
|
||||
var classifier = b.Features.OfType<ComponentDocumentClassifierPass>().Single();
|
||||
classifier.MangleClassNames = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,4 @@
|
|||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="15.6.46" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Components.Razor.Extensions\Microsoft.AspNetCore.Components.Razor.Extensions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
<ItemGroup>
|
||||
<!-- Assemblies built by this project -->
|
||||
<SignedPackageFile Include="$(TargetFileName)" Certificate="$(AssemblySigningCertName)" />
|
||||
<SignedPackageFile Include="Microsoft.AspNetCore.Components.TagHelperWorkaround.dll" Certificate="$(AssemblySigningCertName)" />
|
||||
<SignedPackageFile Include="Microsoft.AspNetCore.Components.Browser.JS.dll" Certificate="$(AssemblySigningCertName)" />
|
||||
<SignedPackageFile Include="Microsoft.AspNetCore.Components.Build.exe" Certificate="$(AssemblySigningCertName)" />
|
||||
|
||||
|
|
@ -31,10 +30,6 @@
|
|||
<SignedPackageFile Include="Mono.Cecil.Rocks.dll" Certificate="$(AssemblySigning3rdPartyCertName)" />
|
||||
|
||||
<!-- Assemblies which should be signed by other build. -->
|
||||
<ExcludePackageFileFromSigning Include="Microsoft.AspNetCore.Razor.Language.dll" />
|
||||
<ExcludePackageFileFromSigning Include="Microsoft.CodeAnalysis.CSharp.dll" />
|
||||
<ExcludePackageFileFromSigning Include="Microsoft.CodeAnalysis.dll" />
|
||||
<ExcludePackageFileFromSigning Include="Microsoft.CodeAnalysis.Razor.dll" />
|
||||
<ExcludePackageFileFromSigning Include="Microsoft.Extensions.CommandLineUtils.dll" />
|
||||
<ExcludePackageFileFromSigning Include="Microsoft.Extensions.FileProviders.Abstractions.dll" />
|
||||
<ExcludePackageFileFromSigning Include="Microsoft.Extensions.FileProviders.Composite.dll" />
|
||||
|
|
@ -67,13 +62,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Components.Browser.JS\Microsoft.AspNetCore.Components.Browser.JS.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Components.Razor.Extensions\Microsoft.AspNetCore.Components.Razor.Extensions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Components.TagHelperWorkaround\Microsoft.AspNetCore.Components.TagHelperWorkaround.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Components\Microsoft.AspNetCore.Components.csproj" />
|
||||
|
||||
<!-- Intentionally include Razor.Design's MSBuild assets for everyone who reference Blazor.Build -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="$(MicrosoftAspNetCoreRazorDesignPackageVersion)" PrivateAssets="None" />
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Composite" Version="$(AspNetCorePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="$(AspNetCorePackageVersion)" />
|
||||
|
|
|
|||
|
|
@ -13,14 +13,12 @@
|
|||
<repository type="git" url="https://github.com/aspnet/aspnetcore" commit="$repositorycommit$" />
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<dependencies>
|
||||
<dependency id="Microsoft.AspNetCore.Razor.Design" version="$razorversion$" include="all" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.Analyzers" version="$version$" include="all" />
|
||||
<dependency id="Microsoft.AspNetCore.Blazor.Mono" version="$blazormonoversion$" include="all" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\..\THIRD-PARTY-NOTICES.txt" />
|
||||
<file src="$publishdir$netcoreapp3.0\Microsoft.AspNetCore.Components.TagHelperWorkaround.dll" target="lib/netstandard1.0/" />
|
||||
<file src="build\**" target="build" />
|
||||
<file src="targets\**" target="targets" />
|
||||
<file src="$publishdir$netcoreapp3.0\**\*" target="tools/" />
|
||||
|
|
|
|||
|
|
@ -33,12 +33,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="$(MicrosoftAspNetCoreRazorDesignPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- This is used as a P2P when building the repo. Normal Blazor projects will get this as a reference through the Blazor.Build package -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Microsoft.AspNetCore.Components.TagHelperWorkaround\Microsoft.AspNetCore.Components.TagHelperWorkaround.csproj" PrivateAssets="all" />
|
||||
<!-- Ensures these projects are built before the consuming project, but without
|
||||
adding a runtime dependency on the .dll (to be equivalent to a <PackageDependency>
|
||||
given that the packed version of this project wouldn't add a .dll reference) -->
|
||||
|
|
|
|||
|
|
@ -1,519 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class BindLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
// Run after event handler pass
|
||||
public override int Order => 100;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
// Nothing to do, bail. We can't function without the standard structure.
|
||||
return;
|
||||
}
|
||||
|
||||
// For each bind *usage* we need to rewrite the tag helper node to map to basic constructs.
|
||||
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
|
||||
|
||||
var parents = new HashSet<IntermediateNode>();
|
||||
for (var i = 0; i < references.Count; i++)
|
||||
{
|
||||
parents.Add(references[i].Parent);
|
||||
}
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
ProcessDuplicates(parent);
|
||||
}
|
||||
|
||||
for (var i = 0; i < references.Count; i++)
|
||||
{
|
||||
var reference = references[i];
|
||||
var node = (TagHelperPropertyIntermediateNode)reference.Node;
|
||||
|
||||
if (!reference.Parent.Children.Contains(node))
|
||||
{
|
||||
// This node was removed as a duplicate, skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.TagHelper.IsBindTagHelper() && node.AttributeName.StartsWith("bind"))
|
||||
{
|
||||
// Workaround for https://github.com/aspnet/Blazor/issues/703
|
||||
var rewritten = RewriteUsage(reference.Parent, node);
|
||||
reference.Remove();
|
||||
|
||||
for (var j = 0; j < rewritten.Length; j++)
|
||||
{
|
||||
reference.Parent.Children.Add(rewritten[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDuplicates(IntermediateNode node)
|
||||
{
|
||||
// Reverse order because we will remove nodes.
|
||||
//
|
||||
// Each 'property' node could be duplicated if there are multiple tag helpers that match that
|
||||
// particular attribute. This is common in our approach, which relies on 'fallback' tag helpers
|
||||
// that overlap with more specific ones.
|
||||
for (var i = node.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
// For each usage of the general 'fallback' bind tag helper, it could duplicate
|
||||
// the usage of a more specific one. Look for duplicates and remove the fallback.
|
||||
var attribute = node.Children[i] as TagHelperPropertyIntermediateNode;
|
||||
if (attribute != null &&
|
||||
attribute.TagHelper != null &&
|
||||
attribute.TagHelper.IsFallbackBindTagHelper())
|
||||
{
|
||||
for (var j = 0; j < node.Children.Count; j++)
|
||||
{
|
||||
var duplicate = node.Children[j] as TagHelperPropertyIntermediateNode;
|
||||
if (duplicate != null &&
|
||||
duplicate.TagHelper != null &&
|
||||
duplicate.TagHelper.IsBindTagHelper() &&
|
||||
duplicate.AttributeName == attribute.AttributeName &&
|
||||
!object.ReferenceEquals(attribute, duplicate))
|
||||
{
|
||||
// Found a duplicate - remove the 'fallback' in favor of the
|
||||
// more specific tag helper.
|
||||
node.Children.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also treat the general <input bind="..." /> as a 'fallback' for that case and remove it.
|
||||
// This is a workaround for a limitation where you can't write a tag helper that binds only
|
||||
// when a specific attribute is **not** present.
|
||||
if (attribute != null &&
|
||||
attribute.TagHelper != null &&
|
||||
attribute.TagHelper.IsInputElementFallbackBindTagHelper())
|
||||
{
|
||||
for (var j = 0; j < node.Children.Count; j++)
|
||||
{
|
||||
var duplicate = node.Children[j] as TagHelperPropertyIntermediateNode;
|
||||
if (duplicate != null &&
|
||||
duplicate.TagHelper != null &&
|
||||
duplicate.TagHelper.IsInputElementBindTagHelper() &&
|
||||
duplicate.AttributeName == attribute.AttributeName &&
|
||||
!object.ReferenceEquals(attribute, duplicate))
|
||||
{
|
||||
// Found a duplicate - remove the 'fallback' input tag helper in favor of the
|
||||
// more specific tag helper.
|
||||
node.Children.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we still have duplicates at this point then they are genuine conflicts.
|
||||
var duplicates = node.Children
|
||||
.OfType<TagHelperPropertyIntermediateNode>()
|
||||
.GroupBy(p => p.AttributeName)
|
||||
.Where(g => g.Count() > 1);
|
||||
|
||||
foreach (var duplicate in duplicates)
|
||||
{
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.CreateBindAttribute_Duplicates(
|
||||
node.Source,
|
||||
duplicate.Key,
|
||||
duplicate.ToArray()));
|
||||
foreach (var property in duplicate)
|
||||
{
|
||||
node.Children.Remove(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntermediateNode[] RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
// Bind works similarly to a macro, it always expands to code that the user could have written.
|
||||
//
|
||||
// For the nodes that are related to the bind-attribute rewrite them to look like a pair of
|
||||
// 'normal' HTML attributes similar to the following transformation.
|
||||
//
|
||||
// Input: <MyComponent bind-Value="@currentCount" />
|
||||
// Output: <MyComponent Value ="...<get the value>..." ValueChanged ="... <set the value>..." />
|
||||
//
|
||||
// This means that the expression that appears inside of 'bind' must be an LValue or else
|
||||
// there will be errors. In general the errors that come from C# in this case are good enough
|
||||
// to understand the problem.
|
||||
//
|
||||
// The BindMethods calls are required in this case because to give us a good experience. They
|
||||
// use overloading to ensure that can get an Action<object> that will convert and set an arbitrary
|
||||
// value.
|
||||
//
|
||||
// We also assume that the element will be treated as a component for now because
|
||||
// multiple passes handle 'special' tag helpers. We have another pass that translates
|
||||
// a tag helper node back into 'regular' element when it doesn't have an associated component
|
||||
if (!TryComputeAttributeNames(
|
||||
parent,
|
||||
node,
|
||||
node.AttributeName,
|
||||
out var valueAttributeName,
|
||||
out var changeAttributeName,
|
||||
out var valueAttribute,
|
||||
out var changeAttribute))
|
||||
{
|
||||
// Skip anything we can't understand. It's important that we don't crash, that will bring down
|
||||
// the build.
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.CreateBindAttribute_InvalidSyntax(
|
||||
node.Source,
|
||||
node.AttributeName));
|
||||
return new[] { node };
|
||||
}
|
||||
|
||||
var original = GetAttributeContent(node);
|
||||
if (string.IsNullOrEmpty(original.Content))
|
||||
{
|
||||
// This can happen in error cases, the parser will already have flagged this
|
||||
// as an error, so ignore it.
|
||||
return new[] { node };
|
||||
}
|
||||
|
||||
// Look for a matching format node. If we find one then we need to pass the format into the
|
||||
// two nodes we generate.
|
||||
IntermediateToken format = null;
|
||||
if (TryGetFormatNode(
|
||||
parent,
|
||||
node,
|
||||
valueAttributeName,
|
||||
out var formatNode))
|
||||
{
|
||||
// Don't write the format out as its own attribute, just capture it as a string
|
||||
// or expression.
|
||||
parent.Children.Remove(formatNode);
|
||||
format = GetAttributeContent(formatNode);
|
||||
}
|
||||
|
||||
// Now rewrite the content of the value node to look like:
|
||||
//
|
||||
// BindMethods.GetValue(<code>) OR
|
||||
// BindMethods.GetValue(<code>, <format>)
|
||||
var valueExpressionTokens = new List<IntermediateToken>();
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = $"{ComponentsApi.BindMethods.GetValue}(",
|
||||
Kind = TokenKind.CSharp
|
||||
});
|
||||
valueExpressionTokens.Add(original);
|
||||
if (!string.IsNullOrEmpty(format?.Content))
|
||||
{
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ", ",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
valueExpressionTokens.Add(format);
|
||||
}
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ")",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
|
||||
// Now rewrite the content of the change-handler node. There are two cases we care about
|
||||
// here. If it's a component attribute, then don't use the 'BindMethods wrapper. We expect
|
||||
// component attributes to always 'match' on type.
|
||||
//
|
||||
// __value => <code> = __value
|
||||
//
|
||||
// For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
|
||||
// so we use BindMethods.SetValueHandler
|
||||
//
|
||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR
|
||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>)
|
||||
//
|
||||
// Note that the linemappings here are applied to the value attribute, not the change attribute.
|
||||
|
||||
string changeExpressionContent = null;
|
||||
if (changeAttribute == null && format == null)
|
||||
{
|
||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
|
||||
}
|
||||
else if (changeAttribute == null && format != null)
|
||||
{
|
||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
|
||||
}
|
||||
else
|
||||
{
|
||||
changeExpressionContent = $"__value => {original.Content} = __value";
|
||||
}
|
||||
var changeExpressionTokens = new List<IntermediateToken>()
|
||||
{
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = changeExpressionContent,
|
||||
Kind = TokenKind.CSharp
|
||||
}
|
||||
};
|
||||
|
||||
if (parent is HtmlElementIntermediateNode)
|
||||
{
|
||||
var valueNode = new HtmlAttributeIntermediateNode()
|
||||
{
|
||||
AttributeName = valueAttributeName,
|
||||
Source = node.Source,
|
||||
|
||||
Prefix = valueAttributeName + "=\"",
|
||||
Suffix = "\"",
|
||||
};
|
||||
|
||||
for (var i = 0; i < node.Diagnostics.Count; i++)
|
||||
{
|
||||
valueNode.Diagnostics.Add(node.Diagnostics[i]);
|
||||
}
|
||||
|
||||
valueNode.Children.Add(new CSharpExpressionAttributeValueIntermediateNode());
|
||||
for (var i = 0; i < valueExpressionTokens.Count; i++)
|
||||
{
|
||||
valueNode.Children[0].Children.Add(valueExpressionTokens[i]);
|
||||
}
|
||||
|
||||
var changeNode = new HtmlAttributeIntermediateNode()
|
||||
{
|
||||
AttributeName = changeAttributeName,
|
||||
Source = node.Source,
|
||||
|
||||
Prefix = changeAttributeName + "=\"",
|
||||
Suffix = "\"",
|
||||
};
|
||||
|
||||
changeNode.Children.Add(new CSharpExpressionAttributeValueIntermediateNode());
|
||||
for (var i = 0; i < changeExpressionTokens.Count; i++)
|
||||
{
|
||||
changeNode.Children[0].Children.Add(changeExpressionTokens[i]);
|
||||
}
|
||||
|
||||
return new[] { valueNode, changeNode };
|
||||
}
|
||||
else
|
||||
{
|
||||
var valueNode = new ComponentAttributeExtensionNode(node)
|
||||
{
|
||||
AttributeName = valueAttributeName,
|
||||
BoundAttribute = valueAttribute, // Might be null if it doesn't match a component attribute
|
||||
PropertyName = valueAttribute?.GetPropertyName(),
|
||||
TagHelper = valueAttribute == null ? null : node.TagHelper,
|
||||
TypeName = valueAttribute?.IsWeaklyTyped() == false ? valueAttribute.TypeName : null,
|
||||
};
|
||||
|
||||
valueNode.Children.Clear();
|
||||
valueNode.Children.Add(new CSharpExpressionIntermediateNode());
|
||||
for (var i = 0; i < valueExpressionTokens.Count; i++)
|
||||
{
|
||||
valueNode.Children[0].Children.Add(valueExpressionTokens[i]);
|
||||
}
|
||||
|
||||
var changeNode = new ComponentAttributeExtensionNode(node)
|
||||
{
|
||||
AttributeName = changeAttributeName,
|
||||
BoundAttribute = changeAttribute, // Might be null if it doesn't match a component attribute
|
||||
PropertyName = changeAttribute?.GetPropertyName(),
|
||||
TagHelper = changeAttribute == null ? null : node.TagHelper,
|
||||
TypeName = changeAttribute?.IsWeaklyTyped() == false ? changeAttribute.TypeName : null,
|
||||
};
|
||||
|
||||
changeNode.Children.Clear();
|
||||
changeNode.Children.Add(new CSharpExpressionIntermediateNode());
|
||||
for (var i = 0; i < changeExpressionTokens.Count; i++)
|
||||
{
|
||||
changeNode.Children[0].Children.Add(changeExpressionTokens[i]);
|
||||
}
|
||||
|
||||
return new[] { valueNode, changeNode };
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryParseBindAttribute(
|
||||
string attributeName,
|
||||
out string valueAttributeName,
|
||||
out string changeAttributeName)
|
||||
{
|
||||
valueAttributeName = null;
|
||||
changeAttributeName = null;
|
||||
|
||||
if (!attributeName.StartsWith("bind"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attributeName == "bind")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var segments = attributeName.Split('-');
|
||||
for (var i = 0; i < segments.Length; i++)
|
||||
{
|
||||
if (string.IsNullOrEmpty(segments[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (segments.Length)
|
||||
{
|
||||
case 2:
|
||||
valueAttributeName = segments[1];
|
||||
return true;
|
||||
|
||||
case 3:
|
||||
changeAttributeName = segments[2];
|
||||
valueAttributeName = segments[1];
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempts to compute the attribute names that should be used for an instance of 'bind'.
|
||||
private bool TryComputeAttributeNames(
|
||||
IntermediateNode parent,
|
||||
TagHelperPropertyIntermediateNode node,
|
||||
string attributeName,
|
||||
out string valueAttributeName,
|
||||
out string changeAttributeName,
|
||||
out BoundAttributeDescriptor valueAttribute,
|
||||
out BoundAttributeDescriptor changeAttribute)
|
||||
{
|
||||
valueAttribute = null;
|
||||
changeAttribute = null;
|
||||
|
||||
// Even though some of our 'bind' tag helpers specify the attribute names, they
|
||||
// should still satisfy one of the valid syntaxes.
|
||||
if (!TryParseBindAttribute(attributeName, out valueAttributeName, out changeAttributeName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The tag helper specifies attribute names, they should win.
|
||||
//
|
||||
// This handles cases like <input type="text" bind="@Foo" /> where the tag helper is
|
||||
// generated to match a specific tag and has metadata that identify the attributes.
|
||||
//
|
||||
// We expect 1 bind tag helper per-node.
|
||||
valueAttributeName = node.TagHelper.GetValueAttributeName() ?? valueAttributeName;
|
||||
changeAttributeName = node.TagHelper.GetChangeAttributeName() ?? changeAttributeName;
|
||||
|
||||
// We expect 0-1 components per-node.
|
||||
var componentTagHelper = (parent as ComponentExtensionNode)?.Component;
|
||||
if (componentTagHelper == null)
|
||||
{
|
||||
// If it's not a component node then there isn't too much else to figure out.
|
||||
return attributeName != null && changeAttributeName != null;
|
||||
}
|
||||
|
||||
// If this is a component, we need an attribute name for the value.
|
||||
if (attributeName == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this is a component, then we can infer '<PropertyName>Changed' as the name
|
||||
// of the change event.
|
||||
if (changeAttributeName == null)
|
||||
{
|
||||
changeAttributeName = valueAttributeName + "Changed";
|
||||
}
|
||||
|
||||
for (var i = 0; i < componentTagHelper.BoundAttributes.Count; i++)
|
||||
{
|
||||
var attribute = componentTagHelper.BoundAttributes[i];
|
||||
|
||||
if (string.Equals(valueAttributeName, attribute.Name))
|
||||
{
|
||||
valueAttribute = attribute;
|
||||
}
|
||||
|
||||
if (string.Equals(changeAttributeName, attribute.Name))
|
||||
{
|
||||
changeAttribute = attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetFormatNode(
|
||||
IntermediateNode node,
|
||||
TagHelperPropertyIntermediateNode attributeNode,
|
||||
string valueAttributeName,
|
||||
out TagHelperPropertyIntermediateNode formatNode)
|
||||
{
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var child = node.Children[i] as TagHelperPropertyIntermediateNode;
|
||||
if (child != null &&
|
||||
child.TagHelper != null &&
|
||||
child.TagHelper == attributeNode.TagHelper &&
|
||||
child.AttributeName == "format-" + valueAttributeName)
|
||||
{
|
||||
formatNode = child;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
formatNode = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
|
||||
if (template != null)
|
||||
{
|
||||
// See comments in TemplateDiagnosticPass
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_TemplateInvalidLocation(template.Source));
|
||||
return new IntermediateToken() { Kind = TokenKind.CSharp, Content = string.Empty, };
|
||||
}
|
||||
|
||||
if (node.Children[0] is HtmlContentIntermediateNode htmlContentNode)
|
||||
{
|
||||
// This case can be hit for a 'string' attribute. We want to turn it into
|
||||
// an expression.
|
||||
var content = "\"" + string.Join(string.Empty, htmlContentNode.Children.OfType<IntermediateToken>().Select(t => t.Content)) + "\"";
|
||||
return new IntermediateToken() { Kind = TokenKind.CSharp, Content = content };
|
||||
}
|
||||
else if (node.Children[0] is CSharpExpressionIntermediateNode cSharpNode)
|
||||
{
|
||||
// This case can be hit when the attribute has an explicit @ inside, which
|
||||
// 'escapes' any special sugar we provide for codegen.
|
||||
return GetToken(cSharpNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the common case for 'mixed' content
|
||||
return GetToken(node);
|
||||
}
|
||||
|
||||
// In error cases we won't have a single token, but we still want to generate the code.
|
||||
IntermediateToken GetToken(IntermediateNode parent)
|
||||
{
|
||||
return
|
||||
parent.Children.Count == 1 ? (IntermediateToken)parent.Children[0] : new IntermediateToken()
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = string.Join(string.Empty, parent.Children.OfType<IntermediateToken>().Select(t => t.Content)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,492 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class BindTagHelperDescriptorProvider : ITagHelperDescriptorProvider
|
||||
{
|
||||
// Run after the component tag helper provider, because we need to see the results.
|
||||
public int Order { get; set; } = 1000;
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
// This provider returns tag helper information for 'bind' which doesn't necessarily
|
||||
// map to any real component. Bind behaviors more like a macro, which can map a single LValue to
|
||||
// both a 'value' attribute and a 'value changed' attribute.
|
||||
//
|
||||
// User types:
|
||||
// <input type="text" bind="@FirstName"/>
|
||||
//
|
||||
// We generate:
|
||||
// <input type="text"
|
||||
// value="@BindMethods.GetValue(FirstName)"
|
||||
// onchange="@BindMethods.SetValue(__value => FirstName = __value, FirstName)"/>
|
||||
//
|
||||
// This isn't very different from code the user could write themselves - thus the pronouncement
|
||||
// that bind is very much like a macro.
|
||||
//
|
||||
// A lot of the value that provide in this case is that the associations between the
|
||||
// elements, and the attributes aren't straightforward.
|
||||
//
|
||||
// For instance on <input type="text" /> we need to listen to 'value' and 'onchange',
|
||||
// but on <input type="checked" we need to listen to 'checked' and 'onchange'.
|
||||
//
|
||||
// We handle a few different cases here:
|
||||
//
|
||||
// 1. When given an attribute like **anywhere**'bind-value-onchange="@FirstName"' we will
|
||||
// generate the 'value' attribute and 'onchange' attribute.
|
||||
//
|
||||
// We don't do any transformation or inference for this case, because the developer has
|
||||
// told us exactly what to do. This is the *full* form of bind, and should support any
|
||||
// combination of element, component, and attributes.
|
||||
//
|
||||
// This is the most general case, and is implemented with a built-in tag helper that applies
|
||||
// to everything, and binds to a dictionary of attributes that start with bind-.
|
||||
//
|
||||
// 2. We also support cases like 'bind-value="@FirstName"' where we will generate the 'value'
|
||||
// attribute and another attribute based for a changed handler based on the metadata.
|
||||
//
|
||||
// These mappings are provided by attributes that tell us what attributes, suffixes, and
|
||||
// elements to map.
|
||||
//
|
||||
// 3. When given an attribute like 'bind="@FirstName"' we will generate a value and change
|
||||
// attribute solely based on the context. We need the context of an HTML tag to know
|
||||
// what attributes to generate.
|
||||
//
|
||||
// Similar to case #2, this should 'just work' from the users point of view. We expect
|
||||
// using this syntax most frequently with input elements.
|
||||
//
|
||||
// These mappings are also provided by attributes. Primarily these are used by <input />
|
||||
// and so we have a special case for input elements and their type attributes.
|
||||
//
|
||||
// 4. For components, we have a bit of a special case. We can infer a syntax that matches
|
||||
// case #2 based on property names. So if a component provides both 'Value' and 'ValueChanged'
|
||||
// we will turn that into an instance of bind.
|
||||
//
|
||||
// So case #1 here is the most general case. Case #2 and #3 are data-driven based on attribute data
|
||||
// we have. Case #4 is data-driven based on component definitions.
|
||||
//
|
||||
// We provide a good set of attributes that map to the HTML dom. This set is user extensible.
|
||||
var compilation = context.GetCompilation();
|
||||
if (compilation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bindMethods = compilation.GetTypeByMetadataName(ComponentsApi.BindMethods.FullTypeName);
|
||||
if (bindMethods == null)
|
||||
{
|
||||
// If we can't find BindMethods, then just bail. We won't be able to compile the
|
||||
// generated code anyway.
|
||||
return;
|
||||
}
|
||||
|
||||
// Tag Helper defintion for case #1. This is the most general case.
|
||||
context.Results.Add(CreateFallbackBindTagHelper());
|
||||
|
||||
// For case #2 & #3 we have a whole bunch of attribute entries on BindMethods that we can use
|
||||
// to data-drive the definitions of these tag helpers.
|
||||
var elementBindData = GetElementBindData(compilation);
|
||||
|
||||
// Case #2 & #3
|
||||
foreach (var tagHelper in CreateElementBindTagHelpers(elementBindData))
|
||||
{
|
||||
context.Results.Add(tagHelper);
|
||||
}
|
||||
|
||||
// For case #4 we look at the tag helpers that were already created corresponding to components
|
||||
// and pattern match on properties.
|
||||
foreach (var tagHelper in CreateComponentBindTagHelpers(context.Results))
|
||||
{
|
||||
context.Results.Add(tagHelper);
|
||||
}
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateFallbackBindTagHelper()
|
||||
{
|
||||
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Bind.TagHelperKind, "Bind", ComponentsApi.AssemblyName);
|
||||
builder.Documentation = Resources.BindTagHelper_Fallback_Documentation;
|
||||
|
||||
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind);
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Bind.RuntimeName;
|
||||
builder.Metadata[BlazorMetadata.Bind.FallbackKey] = bool.TrueString;
|
||||
|
||||
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
builder.SetTypeName("Microsoft.AspNetCore.Components.Bind");
|
||||
|
||||
builder.TagMatchingRule(rule =>
|
||||
{
|
||||
rule.TagName = "*";
|
||||
rule.Attribute(attribute =>
|
||||
{
|
||||
attribute.Name = "bind-";
|
||||
attribute.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch;
|
||||
});
|
||||
});
|
||||
|
||||
builder.BindAttribute(attribute =>
|
||||
{
|
||||
attribute.Documentation = Resources.BindTagHelper_Fallback_Documentation;
|
||||
|
||||
attribute.Name = "bind-...";
|
||||
attribute.AsDictionary("bind-", typeof(object).FullName);
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
attribute.SetPropertyName("Bind");
|
||||
attribute.TypeName = "System.Collections.Generic.Dictionary<string, object>";
|
||||
});
|
||||
|
||||
builder.BindAttribute(attribute =>
|
||||
{
|
||||
attribute.Documentation = Resources.BindTagHelper_Fallback_Format_Documentation;
|
||||
|
||||
attribute.Name = "format-...";
|
||||
attribute.AsDictionary("format-", typeof(string).FullName);
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
attribute.SetPropertyName("Format");
|
||||
attribute.TypeName = "System.Collections.Generic.Dictionary<string, string>";
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private List<ElementBindData> GetElementBindData(Compilation compilation)
|
||||
{
|
||||
var bindElement = compilation.GetTypeByMetadataName(ComponentsApi.BindElementAttribute.FullTypeName);
|
||||
var bindInputElement = compilation.GetTypeByMetadataName(ComponentsApi.BindInputElementAttribute.FullTypeName);
|
||||
|
||||
if (bindElement == null || bindInputElement == null)
|
||||
{
|
||||
// This won't likely happen, but just in case.
|
||||
return new List<ElementBindData>();
|
||||
}
|
||||
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = new BindElementDataVisitor(types);
|
||||
|
||||
// Visit the primary output of this compilation, as well as all references.
|
||||
visitor.Visit(compilation.Assembly);
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
// We ignore .netmodules here - there really isn't a case where they are used by user code
|
||||
// even though the Roslyn APIs all support them.
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
visitor.Visit(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
var results = new List<ElementBindData>();
|
||||
|
||||
for (var i = 0; i < types.Count; i++)
|
||||
{
|
||||
var type = types[i];
|
||||
var attributes = type.GetAttributes();
|
||||
|
||||
// Not handling duplicates here for now since we're the primary ones extending this.
|
||||
// If we see users adding to the set of 'bind' constructs we will want to add deduplication
|
||||
// and potentially diagnostics.
|
||||
for (var j = 0; j < attributes.Length; j++)
|
||||
{
|
||||
var attribute = attributes[j];
|
||||
|
||||
if (attribute.AttributeClass == bindElement)
|
||||
{
|
||||
results.Add(new ElementBindData(
|
||||
type.ContainingAssembly.Name,
|
||||
type.ToDisplayString(),
|
||||
(string)attribute.ConstructorArguments[0].Value,
|
||||
null,
|
||||
(string)attribute.ConstructorArguments[1].Value,
|
||||
(string)attribute.ConstructorArguments[2].Value,
|
||||
(string)attribute.ConstructorArguments[3].Value));
|
||||
}
|
||||
else if (attribute.AttributeClass == bindInputElement)
|
||||
{
|
||||
results.Add(new ElementBindData(
|
||||
type.ContainingAssembly.Name,
|
||||
type.ToDisplayString(),
|
||||
"input",
|
||||
(string)attribute.ConstructorArguments[0].Value,
|
||||
(string)attribute.ConstructorArguments[1].Value,
|
||||
(string)attribute.ConstructorArguments[2].Value,
|
||||
(string)attribute.ConstructorArguments[3].Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
private List<TagHelperDescriptor> CreateElementBindTagHelpers(List<ElementBindData> data)
|
||||
{
|
||||
var results = new List<TagHelperDescriptor>();
|
||||
|
||||
for (var i = 0; i < data.Count; i++)
|
||||
{
|
||||
var entry = data[i];
|
||||
|
||||
var name = entry.Suffix == null ? "Bind" : "Bind_" + entry.Suffix;
|
||||
var attributeName = entry.Suffix == null ? "bind" : "bind-" + entry.Suffix;
|
||||
|
||||
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);
|
||||
builder.Documentation = string.Format(
|
||||
Resources.BindTagHelper_Element_Documentation,
|
||||
entry.ValueAttribute,
|
||||
entry.ChangeAttribute);
|
||||
|
||||
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind);
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Bind.RuntimeName;
|
||||
builder.Metadata[BlazorMetadata.Bind.ValueAttribute] = entry.ValueAttribute;
|
||||
builder.Metadata[BlazorMetadata.Bind.ChangeAttribute] = entry.ChangeAttribute;
|
||||
|
||||
if (entry.TypeAttribute != null)
|
||||
{
|
||||
// For entries that map to the <input /> element, we need to be able to know
|
||||
// the difference between <input /> and <input type="text" .../> for which we
|
||||
// want to use the same attributes.
|
||||
//
|
||||
// We provide a tag helper for <input /> that should match all input elements,
|
||||
// but we only want it to be used when a more specific one is used.
|
||||
//
|
||||
// Therefore we use this metadata to know which one is more specific when two
|
||||
// tag helpers match.
|
||||
builder.Metadata[BlazorMetadata.Bind.TypeAttribute] = entry.TypeAttribute;
|
||||
}
|
||||
|
||||
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
builder.SetTypeName(entry.TypeName);
|
||||
|
||||
builder.TagMatchingRule(rule =>
|
||||
{
|
||||
rule.TagName = entry.Element;
|
||||
if (entry.TypeAttribute != null)
|
||||
{
|
||||
rule.Attribute(a =>
|
||||
{
|
||||
a.Name = "type";
|
||||
a.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.FullMatch;
|
||||
a.Value = entry.TypeAttribute;
|
||||
a.ValueComparisonMode = RequiredAttributeDescriptor.ValueComparisonMode.FullMatch;
|
||||
});
|
||||
}
|
||||
|
||||
rule.Attribute(a =>
|
||||
{
|
||||
a.Name = attributeName;
|
||||
a.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.FullMatch;
|
||||
});
|
||||
});
|
||||
|
||||
builder.BindAttribute(a =>
|
||||
{
|
||||
a.Documentation = string.Format(
|
||||
Resources.BindTagHelper_Element_Documentation,
|
||||
entry.ValueAttribute,
|
||||
entry.ChangeAttribute);
|
||||
|
||||
a.Name = attributeName;
|
||||
a.TypeName = typeof(object).FullName;
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
a.SetPropertyName(name);
|
||||
});
|
||||
|
||||
builder.BindAttribute(attribute =>
|
||||
{
|
||||
attribute.Documentation = string.Format(Resources.BindTagHelper_Element_Format_Documentation, attributeName);
|
||||
|
||||
attribute.Name = formatAttributeName;
|
||||
attribute.TypeName = "System.String";
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
attribute.SetPropertyName(formatName);
|
||||
});
|
||||
|
||||
results.Add(builder.Build());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<TagHelperDescriptor> CreateComponentBindTagHelpers(ICollection<TagHelperDescriptor> tagHelpers)
|
||||
{
|
||||
var results = new List<TagHelperDescriptor>();
|
||||
|
||||
foreach (var tagHelper in tagHelpers)
|
||||
{
|
||||
if (!tagHelper.IsComponentTagHelper())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We want to create a 'bind' tag helper everywhere we see a pair of properties like `Foo`, `FooChanged`
|
||||
// where `FooChanged` is a delegate and `Foo` is not.
|
||||
//
|
||||
// The easiest way to figure this out without a lot of backtracking is to look for `FooChanged` and then
|
||||
// try to find a matching "Foo".
|
||||
for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
|
||||
{
|
||||
var changeAttribute = tagHelper.BoundAttributes[i];
|
||||
if (!changeAttribute.Name.EndsWith("Changed") || !changeAttribute.IsDelegateProperty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
BoundAttributeDescriptor valueAttribute = null;
|
||||
var valueAttributeName = changeAttribute.Name.Substring(0, changeAttribute.Name.Length - "Changed".Length);
|
||||
for (var j = 0; j < tagHelper.BoundAttributes.Count; j++)
|
||||
{
|
||||
if (tagHelper.BoundAttributes[j].Name == valueAttributeName && !tagHelper.BoundAttributes[j].IsDelegateProperty())
|
||||
{
|
||||
valueAttribute = tagHelper.BoundAttributes[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valueAttribute == null)
|
||||
{
|
||||
// No matching attribute found.
|
||||
continue;
|
||||
}
|
||||
|
||||
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Bind.TagHelperKind, tagHelper.Name, tagHelper.AssemblyName);
|
||||
builder.DisplayName = tagHelper.DisplayName;
|
||||
builder.Documentation = string.Format(
|
||||
Resources.BindTagHelper_Component_Documentation,
|
||||
valueAttribute.Name,
|
||||
changeAttribute.Name);
|
||||
|
||||
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind);
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Bind.RuntimeName;
|
||||
builder.Metadata[BlazorMetadata.Bind.ValueAttribute] = valueAttribute.Name;
|
||||
builder.Metadata[BlazorMetadata.Bind.ChangeAttribute] = changeAttribute.Name;
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
builder.SetTypeName(tagHelper.GetTypeName());
|
||||
|
||||
// Match the component and attribute name
|
||||
builder.TagMatchingRule(rule =>
|
||||
{
|
||||
rule.TagName = tagHelper.TagMatchingRules.Single().TagName;
|
||||
rule.Attribute(attribute =>
|
||||
{
|
||||
attribute.Name = "bind-" + valueAttribute.Name;
|
||||
attribute.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.FullMatch;
|
||||
});
|
||||
});
|
||||
|
||||
builder.BindAttribute(attribute =>
|
||||
{
|
||||
attribute.Documentation = string.Format(
|
||||
Resources.BindTagHelper_Component_Documentation,
|
||||
valueAttribute.Name,
|
||||
changeAttribute.Name);
|
||||
|
||||
attribute.Name = "bind-" + valueAttribute.Name;
|
||||
attribute.TypeName = valueAttribute.TypeName;
|
||||
attribute.IsEnum = valueAttribute.IsEnum;
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the toolips.
|
||||
attribute.SetPropertyName(valueAttribute.GetPropertyName());
|
||||
});
|
||||
|
||||
results.Add(builder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private struct ElementBindData
|
||||
{
|
||||
public ElementBindData(
|
||||
string assembly,
|
||||
string typeName,
|
||||
string element,
|
||||
string typeAttribute,
|
||||
string suffix,
|
||||
string valueAttribute,
|
||||
string changeAttribute)
|
||||
{
|
||||
Assembly = assembly;
|
||||
TypeName = typeName;
|
||||
Element = element;
|
||||
TypeAttribute = typeAttribute;
|
||||
Suffix = suffix;
|
||||
ValueAttribute = valueAttribute;
|
||||
ChangeAttribute = changeAttribute;
|
||||
}
|
||||
|
||||
public string Assembly { get; }
|
||||
public string TypeName { get; }
|
||||
public string Element { get; }
|
||||
public string TypeAttribute { get; }
|
||||
public string Suffix { get; }
|
||||
public string ValueAttribute { get; }
|
||||
public string ChangeAttribute { get; }
|
||||
}
|
||||
|
||||
private class BindElementDataVisitor : SymbolVisitor
|
||||
{
|
||||
private List<INamedTypeSymbol> _results;
|
||||
|
||||
public BindElementDataVisitor(List<INamedTypeSymbol> results)
|
||||
{
|
||||
_results = results;
|
||||
}
|
||||
|
||||
public override void VisitNamedType(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (symbol.Name == "BindAttributes" && symbol.DeclaredAccessibility == Accessibility.Public)
|
||||
{
|
||||
_results.Add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitNamespace(INamespaceSymbol symbol)
|
||||
{
|
||||
foreach (var member in symbol.GetMembers())
|
||||
{
|
||||
Visit(member);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitAssembly(IAssemblySymbol symbol)
|
||||
{
|
||||
// This as a simple yet high-value optimization that excludes the vast majority of
|
||||
// assemblies that (by definition) can't contain a component.
|
||||
if (symbol.Name != null && !symbol.Name.StartsWith("System.", StringComparison.Ordinal))
|
||||
{
|
||||
Visit(symbol.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Components.Razor
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Directs a <see cref="DocumentWriter"/> to use <see cref="BlazorRuntimeNodeWriter"/>.
|
||||
/// </summary>
|
||||
internal class BlazorCodeTarget : CodeTarget
|
||||
{
|
||||
private readonly RazorCodeGenerationOptions _options;
|
||||
|
||||
public BlazorCodeTarget(RazorCodeGenerationOptions options, IEnumerable<ICodeTargetExtension> extensions)
|
||||
{
|
||||
_options = options;
|
||||
Extensions = extensions.ToArray();
|
||||
}
|
||||
|
||||
public ICodeTargetExtension[] Extensions { get; }
|
||||
|
||||
public override IntermediateNodeWriter CreateNodeWriter()
|
||||
{
|
||||
return _options.DesignTime ? (BlazorNodeWriter)new BlazorDesignTimeNodeWriter() : new BlazorRuntimeNodeWriter();
|
||||
}
|
||||
|
||||
public override TExtension GetExtension<TExtension>()
|
||||
{
|
||||
for (var i = 0; i < Extensions.Length; i++)
|
||||
{
|
||||
var match = Extensions[i] as TExtension;
|
||||
if (match != null)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool HasExtension<TExtension>()
|
||||
{
|
||||
for (var i = 0; i < Extensions.Length; i++)
|
||||
{
|
||||
var match = Extensions[i] as TExtension;
|
||||
if (match != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,775 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// Based on the DesignTimeNodeWriter from Razor repo.
|
||||
internal class BlazorDesignTimeNodeWriter : BlazorNodeWriter
|
||||
{
|
||||
private readonly ScopeStack _scopeStack = new ScopeStack();
|
||||
|
||||
private static readonly string DesignTimeVariable = "__o";
|
||||
|
||||
public override void WriteHtmlBlock(CodeRenderingContext context, HtmlBlockIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public override void WriteHtmlElement(CodeRenderingContext context, HtmlElementIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
context.RenderChildren(node);
|
||||
}
|
||||
|
||||
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
if (node.Source.HasValue)
|
||||
{
|
||||
using (context.CodeWriter.BuildLinePragma(node.Source.Value))
|
||||
{
|
||||
context.AddSourceMappingFor(node);
|
||||
context.CodeWriter.WriteUsing(node.Content);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.CodeWriter.WriteUsing(node.Content);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
if (node.Children.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.Source != null)
|
||||
{
|
||||
using (context.CodeWriter.BuildLinePragma(node.Source.Value))
|
||||
{
|
||||
var offset = DesignTimeVariable.Length + " = ".Length;
|
||||
context.CodeWriter.WritePadding(offset, node.Source, context);
|
||||
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.AddSourceMappingFor(token);
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the expression like a Template or another extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
context.CodeWriter.WriteLine(";");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the expression like a Template or another extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
context.CodeWriter.WriteLine(";");
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
var isWhitespaceStatement = true;
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var token = node.Children[i] as IntermediateToken;
|
||||
if (token == null || !string.IsNullOrWhiteSpace(token.Content))
|
||||
{
|
||||
isWhitespaceStatement = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IDisposable linePragmaScope = null;
|
||||
if (node.Source != null)
|
||||
{
|
||||
if (!isWhitespaceStatement)
|
||||
{
|
||||
linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value);
|
||||
}
|
||||
|
||||
context.CodeWriter.WritePadding(0, node.Source.Value, context);
|
||||
}
|
||||
else if (isWhitespaceStatement)
|
||||
{
|
||||
// Don't write whitespace if there is no line mapping for it.
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.AddSourceMappingFor(token);
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the statement like an extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (linePragmaScope != null)
|
||||
{
|
||||
linePragmaScope.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
context.RenderChildren(node);
|
||||
}
|
||||
|
||||
public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlAttributeValueIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Do nothing, this can't contain code.
|
||||
}
|
||||
|
||||
public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
if (node.Children.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var firstChild = node.Children[0];
|
||||
if (firstChild.Source != null)
|
||||
{
|
||||
using (context.CodeWriter.BuildLinePragma(firstChild.Source.Value))
|
||||
{
|
||||
var offset = DesignTimeVariable.Length + " = ".Length;
|
||||
context.CodeWriter.WritePadding(offset, firstChild.Source, context);
|
||||
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.AddSourceMappingFor(token);
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the expression like a Template or another extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
context.CodeWriter.WriteLine(";");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
if (token.Source != null)
|
||||
{
|
||||
context.AddSourceMappingFor(token);
|
||||
}
|
||||
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the expression like a Template or another extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
context.CodeWriter.WriteLine(";");
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public override void BeginWriteAttribute(CodeWriter codeWriter, string key)
|
||||
{
|
||||
if (codeWriter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeWriter));
|
||||
}
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddAttribute)}")
|
||||
.Write("-1")
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(key)
|
||||
.WriteParameterSeparator();
|
||||
}
|
||||
|
||||
public override void WriteComponent(CodeRenderingContext context, ComponentExtensionNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
if (node.TypeInferenceNode == null)
|
||||
{
|
||||
// Writes something like:
|
||||
//
|
||||
// builder.OpenComponent<MyComponent>(0);
|
||||
// builder.AddAttribute(1, "Foo", ...);
|
||||
// builder.AddAttribute(2, "ChildContent", ...);
|
||||
// builder.AddElementCapture(3, (__value) => _field = __value);
|
||||
// builder.CloseComponent();
|
||||
foreach (var typeArgument in node.TypeArguments)
|
||||
{
|
||||
context.RenderNode(typeArgument);
|
||||
}
|
||||
|
||||
foreach (var attribute in node.Attributes)
|
||||
{
|
||||
context.RenderNode(attribute);
|
||||
}
|
||||
|
||||
if (node.ChildContents.Any())
|
||||
{
|
||||
foreach (var childContent in node.ChildContents)
|
||||
{
|
||||
context.RenderNode(childContent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We eliminate 'empty' child content when building the tree so that usage like
|
||||
// '<MyComponent>\r\n</MyComponent>' doesn't create a child content.
|
||||
//
|
||||
// Consider what would happen if the user's cursor was inside the element. At
|
||||
// design -time we want to render an empty lambda to provide proper scoping
|
||||
// for any code that the user types.
|
||||
context.RenderNode(new ComponentChildContentIntermediateNode()
|
||||
{
|
||||
TypeName = ComponentsApi.RenderFragment.FullTypeName,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var capture in node.Captures)
|
||||
{
|
||||
context.RenderNode(capture);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// When we're doing type inference, we can't write all of the code inline to initialize
|
||||
// the component on the builder. We generate a method elsewhere, and then pass all of the information
|
||||
// to that method. We pass in all of the attribute values + the sequence numbers.
|
||||
//
|
||||
// __Blazor.MyComponent.TypeInference.CreateMyComponent_0(builder, 0, 1, ..., 2, ..., 3, ....);
|
||||
var attributes = node.Attributes.ToList();
|
||||
var childContents = node.ChildContents.ToList();
|
||||
var captures = node.Captures.ToList();
|
||||
var remaining = attributes.Count + childContents.Count + captures.Count;
|
||||
|
||||
context.CodeWriter.Write(node.TypeInferenceNode.FullTypeName);
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(node.TypeInferenceNode.MethodName);
|
||||
context.CodeWriter.Write("(");
|
||||
|
||||
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
context.CodeWriter.Write("-1");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
for (var i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write("-1");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
// Don't type check generics, since we can't actually write the type name.
|
||||
// The type checking with happen anyway since we defined a method and we're generating
|
||||
// a call to it.
|
||||
WriteComponentAttributeInnards(context, attributes[i], canTypeCheck: false);
|
||||
|
||||
remaining--;
|
||||
if (remaining > 0)
|
||||
{
|
||||
context.CodeWriter.Write(", ");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < childContents.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write("-1");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
WriteComponentChildContentInnards(context, childContents[i]);
|
||||
|
||||
remaining--;
|
||||
if (remaining > 0)
|
||||
{
|
||||
context.CodeWriter.Write(", ");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < captures.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write("-1");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
WriteReferenceCaptureInnards(context, captures[i], shouldTypeCheck: false);
|
||||
|
||||
remaining--;
|
||||
if (remaining > 0)
|
||||
{
|
||||
context.CodeWriter.Write(", ");
|
||||
}
|
||||
}
|
||||
|
||||
context.CodeWriter.Write(");");
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Looks like:
|
||||
// __o = 17;
|
||||
context.CodeWriter.Write(DesignTimeVariable);
|
||||
context.CodeWriter.Write(" = ");
|
||||
|
||||
// Following the same design pattern as the runtime codegen
|
||||
WriteComponentAttributeInnards(context, node, canTypeCheck: true);
|
||||
|
||||
context.CodeWriter.Write(";");
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
|
||||
private void WriteComponentAttributeInnards(CodeRenderingContext context, ComponentAttributeExtensionNode node, bool canTypeCheck)
|
||||
{
|
||||
// We limit component attributes to simple cases. However there is still a lot of complexity
|
||||
// to handle here, since there are a few different cases for how an attribute might be structured.
|
||||
//
|
||||
// This roughly follows the design of the runtime writer for simplicity.
|
||||
if (node.AttributeStructure == AttributeStructure.Minimized)
|
||||
{
|
||||
// Minimized attributes always map to 'true'
|
||||
context.CodeWriter.Write("true");
|
||||
}
|
||||
else if (node.Children.Count > 1)
|
||||
{
|
||||
// We don't expect this to happen, we just want to know if it can.
|
||||
throw new InvalidOperationException("Attribute nodes should either be minimized or a single type of content." + string.Join(", ", node.Children));
|
||||
}
|
||||
else if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode)
|
||||
{
|
||||
// We don't actually need the content at designtime, an empty string will do.
|
||||
context.CodeWriter.Write("\"\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
// There are a few different forms that could be used to contain all of the tokens, but we don't really care
|
||||
// exactly what it looks like - we just want all of the content.
|
||||
//
|
||||
// This can include an empty list in some cases like the following (sic):
|
||||
// <MyComponent Value="
|
||||
//
|
||||
// Or a CSharpExpressionIntermediateNode when the attribute has an explicit transition like:
|
||||
// <MyComponent Value="@value" />
|
||||
//
|
||||
// Of a list of tokens directly in the attribute.
|
||||
var tokens = GetCSharpTokens(node);
|
||||
|
||||
if ((node.BoundAttribute?.IsDelegateProperty() ?? false) ||
|
||||
(node.BoundAttribute?.IsChildContentProperty() ?? false))
|
||||
{
|
||||
// We always surround the expression with the delegate constructor. This makes type
|
||||
// inference inside lambdas, and method group conversion do the right thing.
|
||||
if (canTypeCheck)
|
||||
{
|
||||
context.CodeWriter.Write("new ");
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
context.CodeWriter.WriteLine();
|
||||
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
WriteCSharpToken(context, tokens[i]);
|
||||
}
|
||||
|
||||
if (canTypeCheck)
|
||||
{
|
||||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the case when an attribute contains C# code
|
||||
//
|
||||
// If we have a parameter type, then add a type check.
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
context.CodeWriter.Write(">");
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
WriteCSharpToken(context, tokens[i]);
|
||||
}
|
||||
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedsTypeCheck(ComponentAttributeExtensionNode n)
|
||||
{
|
||||
return n.BoundAttribute != null && !n.BoundAttribute.IsWeaklyTyped();
|
||||
}
|
||||
|
||||
IReadOnlyList<IntermediateToken> GetCSharpTokens(ComponentAttributeExtensionNode attribute)
|
||||
{
|
||||
// We generally expect all children to be CSharp, this is here just in case.
|
||||
return attribute.FindDescendantNodes<IntermediateToken>().Where(t => t.IsCSharp).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Writes something like:
|
||||
//
|
||||
// builder.AddAttribute(1, "ChildContent", (RenderFragment)((__builder73) => { ... }));
|
||||
// OR
|
||||
// builder.AddAttribute(1, "ChildContent", (RenderFragment<Person>)((person) => (__builder73) => { ... }));
|
||||
BeginWriteAttribute(context.CodeWriter, node.AttributeName);
|
||||
context.CodeWriter.Write($"({node.TypeName})(");
|
||||
|
||||
WriteComponentChildContentInnards(context, node);
|
||||
|
||||
context.CodeWriter.Write(")");
|
||||
context.CodeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
private void WriteComponentChildContentInnards(CodeRenderingContext context, ComponentChildContentIntermediateNode node)
|
||||
{
|
||||
// Writes something like:
|
||||
//
|
||||
// ((__builder73) => { ... })
|
||||
// OR
|
||||
// ((person) => (__builder73) => { })
|
||||
_scopeStack.OpenComponentScope(
|
||||
context,
|
||||
node.AttributeName,
|
||||
node.IsParameterized ? node.ParameterName : null);
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
_scopeStack.CloseScope(context);
|
||||
}
|
||||
|
||||
public override void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentExtensionNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// At design type we want write the equivalent of:
|
||||
//
|
||||
// __o = typeof(TItem);
|
||||
context.CodeWriter.Write(DesignTimeVariable);
|
||||
context.CodeWriter.Write(" = ");
|
||||
context.CodeWriter.Write("typeof(");
|
||||
|
||||
var tokens = GetCSharpTokens(node);
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
WriteCSharpToken(context, tokens[i]);
|
||||
}
|
||||
|
||||
context.CodeWriter.Write(");");
|
||||
context.CodeWriter.WriteLine();
|
||||
|
||||
IReadOnlyList<IntermediateToken> GetCSharpTokens(ComponentTypeArgumentExtensionNode arg)
|
||||
{
|
||||
// We generally expect all children to be CSharp, this is here just in case.
|
||||
return arg.FindDescendantNodes<IntermediateToken>().Where(t => t.IsCSharp).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Looks like:
|
||||
//
|
||||
// (__builder73) => { ... }
|
||||
_scopeStack.OpenTemplateScope(context);
|
||||
context.RenderChildren(node);
|
||||
_scopeStack.CloseScope(context);
|
||||
}
|
||||
|
||||
public override void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Looks like:
|
||||
//
|
||||
// __field = default(MyComponent);
|
||||
WriteReferenceCaptureInnards(context, node, shouldTypeCheck: true);
|
||||
}
|
||||
|
||||
protected override void WriteReferenceCaptureInnards(CodeRenderingContext context, RefExtensionNode node, bool shouldTypeCheck)
|
||||
{
|
||||
// We specialize this code based on whether or not we can type check. When we're calling into
|
||||
// a type-inferenced component, we can't do the type check. See the comments in WriteTypeInferenceMethod.
|
||||
if (shouldTypeCheck)
|
||||
{
|
||||
// The runtime node writer moves the call elsewhere. At design time we
|
||||
// just want sufficiently similar code that any unknown-identifier or type
|
||||
// errors will be equivalent
|
||||
var captureTypeName = node.IsComponentCapture
|
||||
? node.ComponentCaptureTypeName
|
||||
: ComponentsApi.ElementRef.FullTypeName;
|
||||
WriteCSharpCode(context, new CSharpCodeIntermediateNode
|
||||
{
|
||||
Source = node.Source,
|
||||
Children =
|
||||
{
|
||||
node.IdentifierToken,
|
||||
new IntermediateToken
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $" = default({captureTypeName});"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Looks like:
|
||||
//
|
||||
// (__value) = { _field = (MyComponent)__value; }
|
||||
// OR
|
||||
// (__value) = { _field = (ElementRef)__value; }
|
||||
const string refCaptureParamName = "__value";
|
||||
using (var lambdaScope = context.CodeWriter.BuildLambda(refCaptureParamName))
|
||||
{
|
||||
WriteCSharpCode(context, new CSharpCodeIntermediateNode
|
||||
{
|
||||
Source = node.Source,
|
||||
Children =
|
||||
{
|
||||
node.IdentifierToken,
|
||||
new IntermediateToken
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $" = {refCaptureParamName};"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCSharpToken(CodeRenderingContext context, IntermediateToken token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token.Content))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.Source?.FilePath == null)
|
||||
{
|
||||
context.CodeWriter.Write(token.Content);
|
||||
return;
|
||||
}
|
||||
|
||||
using (context.CodeWriter.BuildLinePragma(token.Source))
|
||||
{
|
||||
context.CodeWriter.WritePadding(0, token.Source.Value, context);
|
||||
context.AddSourceMappingFor(token);
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,323 +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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal static class BlazorDiagnosticFactory
|
||||
{
|
||||
public static readonly RazorDiagnosticDescriptor CodeBlockInAttribute =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9979",
|
||||
() =>
|
||||
"Code blocks delimited by '@{...}' like '@{{ {0} }}' for attributes are no longer supported " +
|
||||
"These features have been changed to use attribute syntax. " +
|
||||
"Use 'attr=\"@(x => {... }\"'.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_CodeBlockInAttribute(SourceSpan? source, string expression)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
CodeBlockInAttribute,
|
||||
source ?? SourceSpan.Undefined,
|
||||
expression);
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor UnclosedTag = new RazorDiagnosticDescriptor(
|
||||
"BL9980",
|
||||
() => "Unclosed tag '{0}' with no matching end tag.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_UnclosedTag(SourceSpan? span, string tagName)
|
||||
{
|
||||
return RazorDiagnostic.Create(UnclosedTag, span ?? SourceSpan.Undefined, tagName);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor UnexpectedClosingTag = new RazorDiagnosticDescriptor(
|
||||
"BL9981",
|
||||
() => "Unexpected closing tag '{0}' with no matching start tag.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_UnexpectedClosingTag(SourceSpan? span, string tagName)
|
||||
{
|
||||
return RazorDiagnostic.Create(UnexpectedClosingTag, span ?? SourceSpan.Undefined, tagName);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor MismatchedClosingTag = new RazorDiagnosticDescriptor(
|
||||
"BL9982",
|
||||
() => "Mismatching closing tag. Found '{0}' but expected '{1}'.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_MismatchedClosingTag(SourceSpan? span, string expectedTagName, string tagName)
|
||||
{
|
||||
return RazorDiagnostic.Create(MismatchedClosingTag, span ?? SourceSpan.Undefined, expectedTagName, tagName);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor UnexpectedClosingTagForVoidElement = new RazorDiagnosticDescriptor(
|
||||
"BL9983",
|
||||
() => "Unexpected closing tag '{0}'. The element '{0}' is a void element, and should be used without a closing tag.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_UnexpectedClosingTagForVoidElement(SourceSpan? span, string tagName)
|
||||
{
|
||||
return RazorDiagnostic.Create(UnexpectedClosingTagForVoidElement, span ?? SourceSpan.Undefined, tagName);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor InvalidHtmlContent = new RazorDiagnosticDescriptor(
|
||||
"BL9984",
|
||||
() => "Found invalid HTML content. Text '{0}'",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_InvalidHtmlContent(SourceSpan? span, string text)
|
||||
{
|
||||
return RazorDiagnostic.Create(InvalidHtmlContent, span ?? SourceSpan.Undefined, text);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor MultipleComponents = new RazorDiagnosticDescriptor(
|
||||
"BL9985",
|
||||
() => "Multiple components use the tag '{0}'. Components: {1}",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_MultipleComponents(SourceSpan? span, string tagName, IEnumerable<TagHelperDescriptor> components)
|
||||
{
|
||||
return RazorDiagnostic.Create(MultipleComponents, span ?? SourceSpan.Undefined, tagName, string.Join(", ", components.Select(c => c.DisplayName)));
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor UnsupportedComplexContent = new RazorDiagnosticDescriptor(
|
||||
"BL9986",
|
||||
() => "Component attributes do not support complex content (mixed C# and markup). Attribute: '{0}', text '{1}'",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_UnsupportedComplexContent(IntermediateNode node, string attributeName)
|
||||
{
|
||||
var content = string.Join("", node.FindDescendantNodes<IntermediateToken>().Select(t => t.Content));
|
||||
return RazorDiagnostic.Create(UnsupportedComplexContent, node.Source ?? SourceSpan.Undefined, attributeName, content);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor PageDirective_CannotBeImported =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9987",
|
||||
() => Resources.PageDirectiveCannotBeImported,
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreatePageDirective_CannotBeImported(SourceSpan source)
|
||||
{
|
||||
var fileName = Path.GetFileName(source.FilePath);
|
||||
var diagnostic = RazorDiagnostic.Create(PageDirective_CannotBeImported, source, PageDirective.Directive.Directive, fileName);
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor PageDirective_MustSpecifyRoute =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9988",
|
||||
() => "The @page directive must specify a route template. The route template must be enclosed in quotes and begin with the '/' character.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreatePageDirective_MustSpecifyRoute(SourceSpan? source)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(PageDirective_MustSpecifyRoute, source ?? SourceSpan.Undefined);
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor BindAttribute_Duplicates =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9989",
|
||||
() => "The attribute '{0}' was matched by multiple bind attributes. Duplicates:{1}",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateBindAttribute_Duplicates(SourceSpan? source, string attribute, TagHelperPropertyIntermediateNode[] attributes)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
BindAttribute_Duplicates,
|
||||
source ?? SourceSpan.Undefined,
|
||||
attribute,
|
||||
Environment.NewLine + string.Join(Environment.NewLine, attributes.Select(p => p.TagHelper.DisplayName)));
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor EventHandler_Duplicates =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9990",
|
||||
() => "The attribute '{0}' was matched by multiple event handlers attributes. Duplicates:{1}",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateEventHandler_Duplicates(SourceSpan? source, string attribute, TagHelperPropertyIntermediateNode[] attributes)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
EventHandler_Duplicates,
|
||||
source ?? SourceSpan.Undefined,
|
||||
attribute,
|
||||
Environment.NewLine + string.Join(Environment.NewLine, attributes.Select(p => p.TagHelper.DisplayName)));
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor BindAttribute_InvalidSyntax =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9991",
|
||||
() => "The attribute names could not be inferred from bind attribute '{0}'. Bind attributes should be of the form" +
|
||||
"'bind', 'bind-value' or 'bind-value-change'",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateBindAttribute_InvalidSyntax(SourceSpan? source, string attribute)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
BindAttribute_InvalidSyntax,
|
||||
source ?? SourceSpan.Undefined,
|
||||
attribute);
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor DisallowedScriptTag = new RazorDiagnosticDescriptor(
|
||||
"BL9992",
|
||||
() => "Script tags should not be placed inside components because they cannot be updated dynamically. To fix this, move the script tag to the 'index.html' file or another static location. For more information see https://go.microsoft.com/fwlink/?linkid=872131",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
// Reserved: BL9993 Component parameters should not be public
|
||||
|
||||
public static RazorDiagnostic Create_DisallowedScriptTag(SourceSpan? source)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(DisallowedScriptTag, source ?? SourceSpan.Undefined);
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor TemplateInvalidLocation =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9994",
|
||||
() => "Razor templates cannot be used in attributes.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_TemplateInvalidLocation(SourceSpan? source)
|
||||
{
|
||||
return RazorDiagnostic.Create(TemplateInvalidLocation, source ?? SourceSpan.Undefined);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ChildContentSetByAttributeAndBody =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9995",
|
||||
() => "The child content property '{0}' is set by both the attribute and the element contents.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ChildContentSetByAttributeAndBody(SourceSpan? source, string attribute)
|
||||
{
|
||||
return RazorDiagnostic.Create(ChildContentSetByAttributeAndBody, source ?? SourceSpan.Undefined, attribute);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ChildContentMixedWithExplicitChildContent =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9996",
|
||||
() => "Unrecognized child content inside component '{0}'. The component '{0}' accepts child content through the " +
|
||||
"following top-level items: {1}.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ChildContentMixedWithExplicitChildContent(SourceSpan? source, ComponentExtensionNode component)
|
||||
{
|
||||
var supportedElements = string.Join(", ", component.Component.GetChildContentProperties().Select(p => $"'{p.Name}'"));
|
||||
return RazorDiagnostic.Create(ChildContentMixedWithExplicitChildContent, source ?? SourceSpan.Undefined, component.TagName, supportedElements);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidAttribute =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9997",
|
||||
() => "Unrecognized attribute '{0}' on child content element '{1}'.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ChildContentHasInvalidAttribute(SourceSpan? source, string attribute, string element)
|
||||
{
|
||||
return RazorDiagnostic.Create(ChildContentHasInvalidAttribute, source ?? SourceSpan.Undefined, attribute, element);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidParameter =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9998",
|
||||
() => "Invalid parameter name. The parameter name attribute '{0}' on child content element '{1}' can only include literal text.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ChildContentHasInvalidParameter(SourceSpan? source, string attribute, string element)
|
||||
{
|
||||
return RazorDiagnostic.Create(ChildContentHasInvalidParameter, source ?? SourceSpan.Undefined, attribute, element);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ChildContentRepeatedParameterName =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9999",
|
||||
() => "The child content element '{0}' of component '{1}' uses the same parameter name ('{2}') as enclosing child content " +
|
||||
"element '{3}' of component '{4}'. Specify the parameter name like: '<{0} Context=\"another_name\"> to resolve the ambiguity",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ChildContentRepeatedParameterName(
|
||||
SourceSpan? source,
|
||||
ComponentChildContentIntermediateNode childContent1,
|
||||
ComponentExtensionNode component1,
|
||||
ComponentChildContentIntermediateNode childContent2,
|
||||
ComponentExtensionNode component2)
|
||||
{
|
||||
Debug.Assert(childContent1.ParameterName == childContent2.ParameterName);
|
||||
Debug.Assert(childContent1.IsParameterized);
|
||||
Debug.Assert(childContent2.IsParameterized);
|
||||
|
||||
return RazorDiagnostic.Create(
|
||||
ChildContentRepeatedParameterName,
|
||||
source ?? SourceSpan.Undefined,
|
||||
childContent1.AttributeName,
|
||||
component1.TagName,
|
||||
childContent1.ParameterName,
|
||||
childContent2.AttributeName,
|
||||
component2.TagName);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor GenericComponentMissingTypeArgument =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL10000",
|
||||
() => "The component '{0}' is missing required type arguments. Specify the missing types using the attributes: {1}.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_GenericComponentMissingTypeArgument(
|
||||
SourceSpan? source,
|
||||
ComponentExtensionNode component,
|
||||
IEnumerable<BoundAttributeDescriptor> attributes)
|
||||
{
|
||||
Debug.Assert(component.Component.IsGenericTypedComponent());
|
||||
|
||||
var attributesText = string.Join(", ", attributes.Select(a => $"'{a.Name}'"));
|
||||
return RazorDiagnostic.Create(GenericComponentMissingTypeArgument, source ?? SourceSpan.Undefined, component.TagName, attributesText);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor GenericComponentTypeInferenceUnderspecified =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL10001",
|
||||
() => "The type of component '{0}' cannot be inferred based on the values provided. Consider specifying the type arguments " +
|
||||
"directly using the following attributes: {1}.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_GenericComponentTypeInferenceUnderspecified(
|
||||
SourceSpan? source,
|
||||
ComponentExtensionNode component,
|
||||
IEnumerable<BoundAttributeDescriptor> attributes)
|
||||
{
|
||||
Debug.Assert(component.Component.IsGenericTypedComponent());
|
||||
|
||||
var attributesText = string.Join(", ", attributes.Select(a => $"'{a.Name}'"));
|
||||
return RazorDiagnostic.Create(GenericComponentTypeInferenceUnderspecified, source ?? SourceSpan.Undefined, component.TagName, attributesText);
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidParameterOnComponent =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL10002",
|
||||
() => "Invalid parameter name. The parameter name attribute '{0}' on component '{1}' can only include literal text.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ChildContentHasInvalidParameterOnComponent(SourceSpan? source, string attribute, string element)
|
||||
{
|
||||
return RazorDiagnostic.Create(ChildContentHasInvalidParameterOnComponent, source ?? SourceSpan.Undefined, attribute, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,136 +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.Components.Razor
|
||||
{
|
||||
/// <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 ComponentDocumentRewritePass());
|
||||
builder.Features.Add(new ScriptTagPass());
|
||||
builder.Features.Add(new ComplexAttributeContentPass());
|
||||
builder.Features.Add(new ComponentLoweringPass());
|
||||
builder.Features.Add(new EventHandlerLoweringPass());
|
||||
builder.Features.Add(new RefLoweringPass());
|
||||
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());
|
||||
|
||||
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
|
||||
builder.Features.Add(new BindTagHelperDescriptorProvider());
|
||||
builder.Features.Add(new EventHandlerTagHelperDescriptorProvider());
|
||||
builder.Features.Add(new RefTagHelperDescriptorProvider());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class BlazorImportProjectFeature : IImportProjectFeature
|
||||
{
|
||||
private const string ImportsFileName = "_ViewImports.cshtml";
|
||||
|
||||
private static readonly char[] PathSeparators = new char[]{ '/', '\\' };
|
||||
|
||||
// Using explicit newlines here to avoid fooling our baseline tests
|
||||
private readonly static string DefaultUsingImportContent =
|
||||
"\r\n" +
|
||||
"@using System\r\n" +
|
||||
"@using System.Collections.Generic\r\n" +
|
||||
"@using System.Linq\r\n" +
|
||||
"@using System.Threading.Tasks\r\n" +
|
||||
"@using " + ComponentsApi.RenderFragment.Namespace + "\r\n"; // Microsoft.AspNetCore.Components
|
||||
|
||||
public RazorProjectEngine ProjectEngine { get; set; }
|
||||
|
||||
public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem)
|
||||
{
|
||||
if (projectItem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectItem));
|
||||
}
|
||||
|
||||
var imports = new List<RazorProjectItem>()
|
||||
{
|
||||
new VirtualProjectItem(DefaultUsingImportContent),
|
||||
new VirtualProjectItem(@"@addTagHelper ""*, Microsoft.AspNetCore.Components"""),
|
||||
};
|
||||
|
||||
// Try and infer a namespace from the project directory. We don't yet have the ability to pass
|
||||
// the namespace through from the project.
|
||||
if (projectItem.PhysicalPath != null && projectItem.FilePath != null)
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
imports.Add(new VirtualProjectItem($@"@addTagHelper ""*, {baseNamespace}"""));
|
||||
}
|
||||
}
|
||||
|
||||
// We add hierarchical imports second so any default directive imports can be overridden.
|
||||
imports.AddRange(GetHierarchicalImports(ProjectEngine.FileSystem, projectItem));
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
// Temporary API until we fully convert to RazorProjectEngine
|
||||
public IEnumerable<RazorProjectItem> GetHierarchicalImports(RazorProject project, RazorProjectItem projectItem)
|
||||
{
|
||||
// We want items in descending order. FindHierarchicalItems returns items in ascending order.
|
||||
return project.FindHierarchicalItems(projectItem.FilePath, ImportsFileName).Reverse();
|
||||
}
|
||||
|
||||
private class VirtualProjectItem : RazorProjectItem
|
||||
{
|
||||
private readonly byte[] _bytes;
|
||||
|
||||
public VirtualProjectItem(string content)
|
||||
{
|
||||
var preamble = Encoding.UTF8.GetPreamble();
|
||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||
|
||||
_bytes = new byte[preamble.Length + contentBytes.Length];
|
||||
preamble.CopyTo(_bytes, 0);
|
||||
contentBytes.CopyTo(_bytes, preamble.Length);
|
||||
}
|
||||
|
||||
public override string BasePath => null;
|
||||
|
||||
public override string FilePath => null;
|
||||
|
||||
public override string PhysicalPath => null;
|
||||
|
||||
public override bool Exists => true;
|
||||
|
||||
public override Stream Read() => new MemoryStream(_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// Metadata used for Blazor's interactions with the tag helper system
|
||||
internal static class BlazorMetadata
|
||||
{
|
||||
// There's a bug in the 15.7 preview 1 Razor that prevents 'Kind' from being serialized
|
||||
// this affects both tooling and build. For now our workaround is to ignore 'Kind' and
|
||||
// use our own metadata entry to denote non-Component tag helpers.
|
||||
public static readonly string SpecialKindKey = "Blazor.IsSpecialKind";
|
||||
|
||||
public static class Bind
|
||||
{
|
||||
public static readonly string RuntimeName = "Blazor.None";
|
||||
|
||||
public readonly static string TagHelperKind = "Blazor.Bind";
|
||||
|
||||
public readonly static string FallbackKey = "Blazor.Bind.Fallback";
|
||||
|
||||
public readonly static string TypeAttribute = "Blazor.Bind.TypeAttribute";
|
||||
|
||||
public readonly static string ValueAttribute = "Blazor.Bind.ValueAttribute";
|
||||
|
||||
public readonly static string ChangeAttribute = "Blazor.Bind.ChangeAttribute";
|
||||
}
|
||||
|
||||
public static class ChildContent
|
||||
{
|
||||
public static readonly string RuntimeName = "Blazor.None";
|
||||
|
||||
public static readonly string TagHelperKind = "Blazor.ChildContent";
|
||||
|
||||
public static readonly string ParameterNameBoundAttributeKind = "Blazor.ChildContentParameterName";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the synthesized attribute used to set a child content parameter.
|
||||
/// </summary>
|
||||
public static readonly string ParameterAttributeName = "Context";
|
||||
|
||||
/// <summary>
|
||||
/// The default name of the child content parameter (unless set by a Context attribute).
|
||||
/// </summary>
|
||||
public static readonly string DefaultParameterName = "context";
|
||||
}
|
||||
|
||||
public static class Component
|
||||
{
|
||||
public static readonly string ChildContentKey = "Blazor.ChildContent";
|
||||
|
||||
public static readonly string ChildContentParameterNameKey = "Blazor.ChildContentParameterName";
|
||||
|
||||
public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature";
|
||||
|
||||
public static readonly string WeaklyTypedKey = "Blazor.IsWeaklyTyped";
|
||||
|
||||
public static readonly string RuntimeName = "Blazor.IComponent";
|
||||
|
||||
public readonly static string TagHelperKind = "Blazor.Component";
|
||||
|
||||
public readonly static string GenericTypedKey = "Blazor.GenericTyped";
|
||||
|
||||
public readonly static string TypeParameterKey = "Blazor.TypeParameter";
|
||||
}
|
||||
|
||||
public static class EventHandler
|
||||
{
|
||||
public static readonly string EventArgsType = "Blazor.EventHandler.EventArgs";
|
||||
|
||||
public static readonly string RuntimeName = "Blazor.None";
|
||||
|
||||
public readonly static string TagHelperKind = "Blazor.EventHandler";
|
||||
}
|
||||
|
||||
public static class Ref
|
||||
{
|
||||
public readonly static string TagHelperKind = "Blazor.Ref";
|
||||
|
||||
public static readonly string RuntimeName = "Blazor.None";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,218 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal abstract class BlazorNodeWriter : IntermediateNodeWriter
|
||||
{
|
||||
public abstract void BeginWriteAttribute(CodeWriter codeWriter, string key);
|
||||
|
||||
public abstract void WriteComponent(CodeRenderingContext context, ComponentExtensionNode node);
|
||||
|
||||
public abstract void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node);
|
||||
|
||||
public abstract void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node);
|
||||
|
||||
public abstract void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentExtensionNode node);
|
||||
|
||||
public abstract void WriteHtmlElement(CodeRenderingContext context, HtmlElementIntermediateNode node);
|
||||
|
||||
public abstract void WriteHtmlBlock(CodeRenderingContext context, HtmlBlockIntermediateNode node);
|
||||
|
||||
public abstract void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node);
|
||||
|
||||
protected abstract void WriteReferenceCaptureInnards(CodeRenderingContext context, RefExtensionNode node, bool shouldTypeCheck);
|
||||
|
||||
public abstract void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node);
|
||||
|
||||
public sealed override void BeginWriterScope(CodeRenderingContext context, string writer)
|
||||
{
|
||||
throw new NotImplementedException(nameof(BeginWriterScope));
|
||||
}
|
||||
|
||||
public sealed override void EndWriterScope(CodeRenderingContext context)
|
||||
{
|
||||
throw new NotImplementedException(nameof(EndWriterScope));
|
||||
}
|
||||
|
||||
public sealed override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node)
|
||||
{
|
||||
// We used to support syntaxes like <elem onsomeevent=@{ /* some C# code */ } /> but this is no longer the
|
||||
// case.
|
||||
//
|
||||
// We provide an error for this case just to be friendly.
|
||||
var content = string.Join("", node.Children.OfType<IntermediateToken>().Select(t => t.Content));
|
||||
context.Diagnostics.Add(BlazorDiagnosticFactory.Create_CodeBlockInAttribute(node.Source, content));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Currently the same for design time and runtime
|
||||
public void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// This is ugly because CodeWriter doesn't allow us to erase, but we need to comma-delimit. So we have to
|
||||
// materizalize something can iterate, or use string.Join. We'll need this multiple times, so materializing
|
||||
// it.
|
||||
var parameters = GetParameterDeclarations();
|
||||
|
||||
// This is really similar to the code in WriteComponentAttribute and WriteComponentChildContent - except simpler because
|
||||
// attributes and child contents look like variables.
|
||||
//
|
||||
// Looks like:
|
||||
//
|
||||
// public static void CreateFoo_0<T1, T2>(RenderTreeBuilder builder, int seq, int __seq0, T1 __arg0, int __seq1, global::System.Collections.Generic.List<T2> __arg1, int __seq2, string __arg2)
|
||||
// {
|
||||
// builder.OpenComponent<Foo<T1, T2>>();
|
||||
// builder.AddAttribute(__seq0, "Attr0", __arg0);
|
||||
// builder.AddAttribute(__seq1, "Attr1", __arg1);
|
||||
// builder.AddAttribute(__seq2, "Attr2", __arg2);
|
||||
// builder.CloseComponent();
|
||||
// }
|
||||
//
|
||||
// As a special case, we need to generate a thunk for captures in this block instead of using
|
||||
// them verbatim.
|
||||
//
|
||||
// The problem is that RenderTreeBuilder wants an Action<object>. The caller can't write the type
|
||||
// name if it contains generics, and we can't write the variable they want to assign to.
|
||||
var writer = context.CodeWriter;
|
||||
|
||||
writer.Write("public static void ");
|
||||
writer.Write(node.MethodName);
|
||||
|
||||
writer.Write("<");
|
||||
writer.Write(string.Join(", ", node.Component.Component.GetTypeParameters().Select(a => a.Name)));
|
||||
writer.Write(">");
|
||||
|
||||
writer.Write("(");
|
||||
writer.Write("global::");
|
||||
writer.Write(ComponentsApi.RenderTreeBuilder.FullTypeName);
|
||||
writer.Write(" builder");
|
||||
writer.Write(", ");
|
||||
writer.Write("int seq");
|
||||
|
||||
if (parameters.Count > 0)
|
||||
{
|
||||
writer.Write(", ");
|
||||
}
|
||||
|
||||
for (var i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
writer.Write("int ");
|
||||
writer.Write(parameters[i].seqName);
|
||||
|
||||
writer.Write(", ");
|
||||
writer.Write(parameters[i].typeName);
|
||||
writer.Write(" ");
|
||||
writer.Write(parameters[i].parameterName);
|
||||
|
||||
if (i < parameters.Count - 1)
|
||||
{
|
||||
writer.Write(", ");
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write(")");
|
||||
writer.WriteLine();
|
||||
|
||||
writer.WriteLine("{");
|
||||
|
||||
// builder.OpenComponent<TComponent>(42);
|
||||
context.CodeWriter.Write("builder");
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(ComponentsApi.RenderTreeBuilder.OpenComponent);
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(node.Component.TypeName);
|
||||
context.CodeWriter.Write(">(");
|
||||
context.CodeWriter.Write("seq");
|
||||
context.CodeWriter.Write(");");
|
||||
context.CodeWriter.WriteLine();
|
||||
|
||||
var index = 0;
|
||||
foreach (var attribute in node.Component.Attributes)
|
||||
{
|
||||
context.CodeWriter.WriteStartInstanceMethodInvocation("builder", ComponentsApi.RenderTreeBuilder.AddAttribute);
|
||||
context.CodeWriter.Write(parameters[index].seqName);
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
context.CodeWriter.Write($"\"{attribute.AttributeName}\"");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
context.CodeWriter.Write(parameters[index].parameterName);
|
||||
context.CodeWriter.WriteEndMethodInvocation();
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
foreach (var childContent in node.Component.ChildContents)
|
||||
{
|
||||
context.CodeWriter.WriteStartInstanceMethodInvocation("builder", ComponentsApi.RenderTreeBuilder.AddAttribute);
|
||||
context.CodeWriter.Write(parameters[index].seqName);
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
context.CodeWriter.Write($"\"{childContent.AttributeName}\"");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
context.CodeWriter.Write(parameters[index].parameterName);
|
||||
context.CodeWriter.WriteEndMethodInvocation();
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
foreach (var capture in node.Component.Captures)
|
||||
{
|
||||
context.CodeWriter.WriteStartInstanceMethodInvocation("builder", capture.IsComponentCapture ? ComponentsApi.RenderTreeBuilder.AddComponentReferenceCapture : ComponentsApi.RenderTreeBuilder.AddElementReferenceCapture);
|
||||
context.CodeWriter.Write(parameters[index].seqName);
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
var cast = capture.IsComponentCapture ? $"({capture.ComponentCaptureTypeName})" : string.Empty;
|
||||
context.CodeWriter.Write($"(__value) => {{ {parameters[index].parameterName}({cast}__value); }}");
|
||||
context.CodeWriter.WriteEndMethodInvocation();
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
context.CodeWriter.WriteInstanceMethodInvocation("builder", ComponentsApi.RenderTreeBuilder.CloseComponent);
|
||||
|
||||
writer.WriteLine("}");
|
||||
|
||||
List<(string seqName, string typeName, string parameterName)> GetParameterDeclarations()
|
||||
{
|
||||
var p = new List<(string seqName, string typeName, string parameterName)>();
|
||||
foreach (var attribute in node.Component.Attributes)
|
||||
{
|
||||
p.Add(($"__seq{p.Count}", attribute.TypeName, $"__arg{p.Count}"));
|
||||
}
|
||||
|
||||
foreach (var childContent in node.Component.ChildContents)
|
||||
{
|
||||
p.Add(($"__seq{p.Count}", childContent.TypeName, $"__arg{p.Count}"));
|
||||
}
|
||||
|
||||
foreach (var capture in node.Component.Captures)
|
||||
{
|
||||
p.Add(($"__seq{p.Count}", capture.TypeName, $"__arg{p.Count}"));
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,766 +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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the C# code corresponding to Razor source document contents.
|
||||
/// </summary>
|
||||
internal class BlazorRuntimeNodeWriter : BlazorNodeWriter
|
||||
{
|
||||
private readonly List<IntermediateToken> _currentAttributeValues = new List<IntermediateToken>();
|
||||
private readonly ScopeStack _scopeStack = new ScopeStack();
|
||||
private int _sourceSequence = 0;
|
||||
|
||||
public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
var isWhitespaceStatement = true;
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var token = node.Children[i] as IntermediateToken;
|
||||
if (token == null || !string.IsNullOrWhiteSpace(token.Content))
|
||||
{
|
||||
isWhitespaceStatement = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isWhitespaceStatement)
|
||||
{
|
||||
// The runtime and design time code differ in their handling of whitespace-only
|
||||
// statements. At runtime we can discard them completely. At design time we need
|
||||
// to keep them for the editor.
|
||||
return;
|
||||
}
|
||||
|
||||
IDisposable linePragmaScope = null;
|
||||
if (node.Source != null)
|
||||
{
|
||||
linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value);
|
||||
context.CodeWriter.WritePadding(0, node.Source.Value, context);
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.AddSourceMappingFor(token);
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the statement like an extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (linePragmaScope != null)
|
||||
{
|
||||
linePragmaScope.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Since we're not in the middle of writing an element, this must evaluate as some
|
||||
// text to display
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddContent)}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator();
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the expression like a Template or another extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
context.CodeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// In cases like "somestring @variable", Razor tokenizes it as:
|
||||
// [0] HtmlContent="somestring"
|
||||
// [1] CsharpContent="variable" Prefix=" "
|
||||
// ... so to avoid losing whitespace, convert the prefix to a further token in the list
|
||||
if (!string.IsNullOrEmpty(node.Prefix))
|
||||
{
|
||||
_currentAttributeValues.Add(new IntermediateToken() { Kind = TokenKind.Html, Content = node.Prefix });
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
_currentAttributeValues.Add((IntermediateToken)node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteHtmlBlock(CodeRenderingContext context, HtmlBlockIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddMarkupContent)}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(node.Content)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void WriteHtmlElement(CodeRenderingContext context, HtmlElementIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.OpenElement)}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(node.TagName)
|
||||
.WriteEndMethodInvocation();
|
||||
|
||||
// Render Attributes before creating the scope.
|
||||
foreach (var attribute in node.Attributes)
|
||||
{
|
||||
context.RenderNode(attribute);
|
||||
}
|
||||
|
||||
foreach (var capture in node.Captures)
|
||||
{
|
||||
context.RenderNode(capture);
|
||||
}
|
||||
|
||||
// Render body of the tag inside the scope
|
||||
foreach (var child in node.Body)
|
||||
{
|
||||
context.RenderNode(child);
|
||||
}
|
||||
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{ComponentsApi.RenderTreeBuilder.CloseElement}")
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
Debug.Assert(_currentAttributeValues.Count == 0);
|
||||
context.RenderChildren(node);
|
||||
|
||||
WriteAttribute(context.CodeWriter, node.AttributeName, _currentAttributeValues);
|
||||
_currentAttributeValues.Clear();
|
||||
}
|
||||
|
||||
public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlAttributeValueIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
var stringContent = ((IntermediateToken)node.Children.Single()).Content;
|
||||
_currentAttributeValues.Add(new IntermediateToken() { Kind = TokenKind.Html, Content = node.Prefix + stringContent, });
|
||||
}
|
||||
|
||||
public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Text node
|
||||
var content = GetHtmlContent(node);
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddContent)}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(content)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
context.CodeWriter.WriteUsing(node.Content, endLine: true);
|
||||
}
|
||||
|
||||
public override void WriteComponent(CodeRenderingContext context, ComponentExtensionNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
if (node.TypeInferenceNode == null)
|
||||
{
|
||||
// If the component is using not using type inference then we just write an open/close with a series
|
||||
// of add attribute calls in between.
|
||||
//
|
||||
// Writes something like:
|
||||
//
|
||||
// builder.OpenComponent<MyComponent>(0);
|
||||
// builder.AddAttribute(1, "Foo", ...);
|
||||
// builder.AddAttribute(2, "ChildContent", ...);
|
||||
// builder.AddElementCapture(3, (__value) => _field = __value);
|
||||
// builder.CloseComponent();
|
||||
|
||||
// builder.OpenComponent<TComponent>(42);
|
||||
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(ComponentsApi.RenderTreeBuilder.OpenComponent);
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
context.CodeWriter.Write(">(");
|
||||
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||
context.CodeWriter.Write(");");
|
||||
context.CodeWriter.WriteLine();
|
||||
|
||||
// We can skip type arguments during runtime codegen, they are handled in the
|
||||
// type/parameter declarations.
|
||||
|
||||
foreach (var attribute in node.Attributes)
|
||||
{
|
||||
context.RenderNode(attribute);
|
||||
}
|
||||
|
||||
foreach (var childContent in node.ChildContents)
|
||||
{
|
||||
context.RenderNode(childContent);
|
||||
}
|
||||
|
||||
foreach (var capture in node.Captures)
|
||||
{
|
||||
context.RenderNode(capture);
|
||||
}
|
||||
|
||||
// builder.CloseComponent();
|
||||
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(ComponentsApi.RenderTreeBuilder.CloseComponent);
|
||||
context.CodeWriter.Write("();");
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
// When we're doing type inference, we can't write all of the code inline to initialize
|
||||
// the component on the builder. We generate a method elsewhere, and then pass all of the information
|
||||
// to that method. We pass in all of the attribute values + the sequence numbers.
|
||||
//
|
||||
// __Blazor.MyComponent.TypeInference.CreateMyComponent_0(builder, 0, 1, ..., 2, ..., 3, ...);
|
||||
var attributes = node.Attributes.ToList();
|
||||
var childContents = node.ChildContents.ToList();
|
||||
var captures = node.Captures.ToList();
|
||||
var remaining = attributes.Count + childContents.Count + captures.Count;
|
||||
|
||||
context.CodeWriter.Write(node.TypeInferenceNode.FullTypeName);
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(node.TypeInferenceNode.MethodName);
|
||||
context.CodeWriter.Write("(");
|
||||
|
||||
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
for (var i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
// Don't type check generics, since we can't actually write the type name.
|
||||
// The type checking with happen anyway since we defined a method and we're generating
|
||||
// a call to it.
|
||||
WriteComponentAttributeInnards(context, attributes[i], canTypeCheck: false);
|
||||
|
||||
remaining--;
|
||||
if (remaining > 0)
|
||||
{
|
||||
context.CodeWriter.Write(", ");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < childContents.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
WriteComponentChildContentInnards(context, childContents[i]);
|
||||
|
||||
remaining--;
|
||||
if (remaining > 0)
|
||||
{
|
||||
context.CodeWriter.Write(", ");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < captures.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
WriteReferenceCaptureInnards(context, captures[i], shouldTypeCheck: false);
|
||||
|
||||
remaining--;
|
||||
if (remaining > 0)
|
||||
{
|
||||
context.CodeWriter.Write(", ");
|
||||
}
|
||||
}
|
||||
|
||||
context.CodeWriter.Write(");");
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// builder.AddAttribute(1, "Foo", 42);
|
||||
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(ComponentsApi.RenderTreeBuilder.AddAttribute);
|
||||
context.CodeWriter.Write("(");
|
||||
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||
context.CodeWriter.Write(", ");
|
||||
context.CodeWriter.WriteStringLiteral(node.AttributeName);
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
WriteComponentAttributeInnards(context, node, canTypeCheck: true);
|
||||
|
||||
context.CodeWriter.Write(");");
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
|
||||
private void WriteComponentAttributeInnards(CodeRenderingContext context, ComponentAttributeExtensionNode node, bool canTypeCheck)
|
||||
{
|
||||
if (node.AttributeStructure == AttributeStructure.Minimized)
|
||||
{
|
||||
// Minimized attributes always map to 'true'
|
||||
context.CodeWriter.Write("true");
|
||||
}
|
||||
else if (node.Children.Count > 1)
|
||||
{
|
||||
// We don't expect this to happen, we just want to know if it can.
|
||||
throw new InvalidOperationException("Attribute nodes should either be minimized or a single type of content." + string.Join(", ", node.Children));
|
||||
}
|
||||
else if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlNode)
|
||||
{
|
||||
// This is how string attributes are lowered by default, a single HTML node with a single HTML token.
|
||||
var content = string.Join(string.Empty, GetHtmlTokens(htmlNode).Select(t => t.Content));
|
||||
context.CodeWriter.WriteStringLiteral(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// See comments in BlazorDesignTimeNodeWriter for a description of the cases that are possible.
|
||||
var tokens = GetCSharpTokens(node);
|
||||
if ((node.BoundAttribute?.IsDelegateProperty() ?? false) ||
|
||||
(node.BoundAttribute?.IsChildContentProperty() ?? false))
|
||||
{
|
||||
if (canTypeCheck)
|
||||
{
|
||||
context.CodeWriter.Write("new ");
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write(tokens[i].Content);
|
||||
}
|
||||
|
||||
if (canTypeCheck)
|
||||
{
|
||||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
context.CodeWriter.Write(">");
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write(tokens[i].Content);
|
||||
}
|
||||
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<IntermediateToken> GetCSharpTokens(ComponentAttributeExtensionNode attribute)
|
||||
{
|
||||
// We generally expect all children to be CSharp, this is here just in case.
|
||||
return attribute.FindDescendantNodes<IntermediateToken>().Where(t => t.IsCSharp).ToArray();
|
||||
}
|
||||
|
||||
IReadOnlyList<IntermediateToken> GetHtmlTokens(HtmlContentIntermediateNode html)
|
||||
{
|
||||
// We generally expect all children to be HTML, this is here just in case.
|
||||
return html.FindDescendantNodes<IntermediateToken>().Where(t => t.IsHtml).ToArray();
|
||||
}
|
||||
|
||||
bool NeedsTypeCheck(ComponentAttributeExtensionNode n)
|
||||
{
|
||||
return node.BoundAttribute != null && !node.BoundAttribute.IsWeaklyTyped();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Writes something like:
|
||||
//
|
||||
// builder.AddAttribute(1, "ChildContent", (RenderFragment)((__builder73) => { ... }));
|
||||
// OR
|
||||
// builder.AddAttribute(1, "ChildContent", (RenderFragment<Person>)((person) => (__builder73) => { ... }));
|
||||
BeginWriteAttribute(context.CodeWriter, node.AttributeName);
|
||||
context.CodeWriter.Write($"({node.TypeName})(");
|
||||
|
||||
WriteComponentChildContentInnards(context, node);
|
||||
|
||||
context.CodeWriter.Write(")");
|
||||
context.CodeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
private void WriteComponentChildContentInnards(CodeRenderingContext context, ComponentChildContentIntermediateNode node)
|
||||
{
|
||||
// Writes something like:
|
||||
//
|
||||
// ((__builder73) => { ... })
|
||||
// OR
|
||||
// ((person) => (__builder73) => { })
|
||||
_scopeStack.OpenComponentScope(
|
||||
context,
|
||||
node.AttributeName,
|
||||
node.IsParameterized ? node.ParameterName : null);
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
_scopeStack.CloseScope(context);
|
||||
}
|
||||
|
||||
public override void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentExtensionNode node)
|
||||
{
|
||||
// We can skip type arguments during runtime codegen, they are handled in the
|
||||
// type/parameter declarations.
|
||||
}
|
||||
|
||||
public override void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
// Looks like:
|
||||
//
|
||||
// (__builder73) => { ... }
|
||||
_scopeStack.OpenTemplateScope(context);
|
||||
context.RenderChildren(node);
|
||||
_scopeStack.CloseScope(context);
|
||||
}
|
||||
|
||||
public override void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node)
|
||||
{
|
||||
// Looks like:
|
||||
//
|
||||
// builder.AddComponentReferenceCapture(2, (__value) = { _field = (MyComponent)__value; });
|
||||
// OR
|
||||
// builder.AddElementReferenceCapture(2, (__value) = { _field = (ElementRef)__value; });
|
||||
var codeWriter = context.CodeWriter;
|
||||
|
||||
var methodName = node.IsComponentCapture
|
||||
? nameof(ComponentsApi.RenderTreeBuilder.AddComponentReferenceCapture)
|
||||
: nameof(ComponentsApi.RenderTreeBuilder.AddElementReferenceCapture);
|
||||
codeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{methodName}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator();
|
||||
|
||||
WriteReferenceCaptureInnards(context, node, shouldTypeCheck: true);
|
||||
|
||||
codeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
protected override void WriteReferenceCaptureInnards(CodeRenderingContext context, RefExtensionNode node, bool shouldTypeCheck)
|
||||
{
|
||||
// Looks like:
|
||||
//
|
||||
// (__value) = { _field = (MyComponent)__value; }
|
||||
// OR
|
||||
// (__value) = { _field = (ElementRef)__value; }
|
||||
const string refCaptureParamName = "__value";
|
||||
using (var lambdaScope = context.CodeWriter.BuildLambda(refCaptureParamName))
|
||||
{
|
||||
var typecastIfNeeded = shouldTypeCheck && node.IsComponentCapture ? $"({node.ComponentCaptureTypeName})" : string.Empty;
|
||||
WriteCSharpCode(context, new CSharpCodeIntermediateNode
|
||||
{
|
||||
Source = node.Source,
|
||||
Children =
|
||||
{
|
||||
node.IdentifierToken,
|
||||
new IntermediateToken
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $" = {typecastIfNeeded}{refCaptureParamName};"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteAttribute(CodeWriter codeWriter, string key, IList<IntermediateToken> value)
|
||||
{
|
||||
BeginWriteAttribute(codeWriter, key);
|
||||
WriteAttributeValue(codeWriter, value);
|
||||
codeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void BeginWriteAttribute(CodeWriter codeWriter, string key)
|
||||
{
|
||||
codeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddAttribute)}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(key)
|
||||
.WriteParameterSeparator();
|
||||
}
|
||||
|
||||
private static string GetHtmlContent(HtmlContentIntermediateNode node)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var htmlTokens = node.Children.OfType<IntermediateToken>().Where(t => t.IsHtml);
|
||||
foreach (var htmlToken in htmlTokens)
|
||||
{
|
||||
builder.Append(htmlToken.Content);
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
// There are a few cases here, we need to handle:
|
||||
// - Pure HTML
|
||||
// - Pure CSharp
|
||||
// - Mixed HTML and CSharp
|
||||
//
|
||||
// Only the mixed case is complicated, we want to turn it into code that will concatenate
|
||||
// the values into a string at runtime.
|
||||
|
||||
private static void WriteAttributeValue(CodeWriter writer, IList<IntermediateToken> tokens)
|
||||
{
|
||||
if (tokens == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tokens));
|
||||
}
|
||||
|
||||
var hasHtml = false;
|
||||
var hasCSharp = false;
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
if (tokens[i].IsCSharp)
|
||||
{
|
||||
hasCSharp |= true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasHtml |= true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHtml && hasCSharp)
|
||||
{
|
||||
// If it's a C# expression, we have to wrap it in parens, otherwise things like ternary
|
||||
// expressions don't compose with concatenation. However, this is a little complicated
|
||||
// because C# tokens themselves aren't guaranteed to be distinct expressions. We want
|
||||
// to treat all contiguous C# tokens as a single expression.
|
||||
var insideCSharp = false;
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
var token = tokens[i];
|
||||
if (token.IsCSharp)
|
||||
{
|
||||
if (!insideCSharp)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
writer.Write(" + ");
|
||||
}
|
||||
|
||||
writer.Write("(");
|
||||
insideCSharp = true;
|
||||
}
|
||||
|
||||
writer.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (insideCSharp)
|
||||
{
|
||||
writer.Write(")");
|
||||
insideCSharp = false;
|
||||
}
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
writer.Write(" + ");
|
||||
}
|
||||
|
||||
writer.WriteStringLiteral(token.Content);
|
||||
}
|
||||
}
|
||||
|
||||
if (insideCSharp)
|
||||
{
|
||||
writer.Write(")");
|
||||
}
|
||||
}
|
||||
else if (hasCSharp)
|
||||
{
|
||||
writer.Write(string.Join("", tokens.Select(t => t.Content)));
|
||||
}
|
||||
else if (hasHtml)
|
||||
{
|
||||
writer.WriteStringLiteral(string.Join("", tokens.Select(t => t.Content)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Minimized attributes always map to 'true'
|
||||
writer.Write("true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +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.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class BlazorTemplateTargetExtension : ITemplateTargetExtension
|
||||
{
|
||||
public void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node)
|
||||
{
|
||||
((BlazorNodeWriter)context.NodeWriter).WriteTemplate(context, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +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.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// Copied from the Razor repo
|
||||
internal static class CSharpIdentifier
|
||||
{
|
||||
private const string CshtmlExtension = ".cshtml";
|
||||
|
||||
public static string GetClassNameFromPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.EndsWith(CshtmlExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
path = path.Substring(0, path.Length - CshtmlExtension.Length);
|
||||
}
|
||||
|
||||
return SanitizeClassName(path);
|
||||
}
|
||||
|
||||
// CSharp Spec §2.4.2
|
||||
private static bool IsIdentifierStart(char character)
|
||||
{
|
||||
return char.IsLetter(character) ||
|
||||
character == '_' ||
|
||||
CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.LetterNumber;
|
||||
}
|
||||
|
||||
public static bool IsIdentifierPart(char character)
|
||||
{
|
||||
return char.IsDigit(character) ||
|
||||
IsIdentifierStart(character) ||
|
||||
IsIdentifierPartByUnicodeCategory(character);
|
||||
}
|
||||
|
||||
private static bool IsIdentifierPartByUnicodeCategory(char character)
|
||||
{
|
||||
var category = CharUnicodeInfo.GetUnicodeCategory(character);
|
||||
|
||||
return category == UnicodeCategory.NonSpacingMark || // Mn
|
||||
category == UnicodeCategory.SpacingCombiningMark || // Mc
|
||||
category == UnicodeCategory.ConnectorPunctuation || // Pc
|
||||
category == UnicodeCategory.Format; // Cf
|
||||
}
|
||||
|
||||
public static string SanitizeClassName(string inputName)
|
||||
{
|
||||
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
|
||||
{
|
||||
inputName = "_" + inputName;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(inputName.Length);
|
||||
for (var i = 0; i < inputName.Length; i++)
|
||||
{
|
||||
var ch = inputName[i];
|
||||
builder.Append(IsIdentifierPart(ch) ? ch : '_');
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +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.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class ChildContentDiagnosticPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
// Runs after components/eventhandlers/ref/bind/templates. We want to validate every component
|
||||
// and it's usage of ChildContent.
|
||||
public override int Order => 160;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(documentNode);
|
||||
}
|
||||
|
||||
private class Visitor : IntermediateNodeWalker, IExtensionIntermediateNodeVisitor<ComponentExtensionNode>, IExtensionIntermediateNodeVisitor<ComponentChildContentIntermediateNode>
|
||||
{
|
||||
public void VisitExtension(ComponentExtensionNode node)
|
||||
{
|
||||
// Check for properties that are set by both element contents (body) and the attribute itself.
|
||||
foreach (var childContent in node.ChildContents)
|
||||
{
|
||||
foreach (var attribute in node.Attributes)
|
||||
{
|
||||
if (attribute.AttributeName == childContent.AttributeName)
|
||||
{
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentSetByAttributeAndBody(
|
||||
attribute.Source,
|
||||
attribute.AttributeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
|
||||
public void VisitExtension(ComponentChildContentIntermediateNode node)
|
||||
{
|
||||
// Check that each child content has a unique parameter name within its scope. This is important
|
||||
// because the parameter name can be implicit, and it doesn't work well when nested.
|
||||
if (node.IsParameterized)
|
||||
{
|
||||
for (var i = 0; i < Ancestors.Count - 1; i++)
|
||||
{
|
||||
var ancestor = Ancestors[i] as ComponentChildContentIntermediateNode;
|
||||
if (ancestor != null &&
|
||||
ancestor.IsParameterized &&
|
||||
string.Equals(node.ParameterName, ancestor.ParameterName, StringComparison.Ordinal))
|
||||
{
|
||||
// Duplicate name. We report an error because this will almost certainly also lead to an error
|
||||
// from the C# compiler that's way less clear.
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentRepeatedParameterName(
|
||||
node.Source,
|
||||
node,
|
||||
(ComponentExtensionNode)Ancestors[0], // Enclosing component
|
||||
ancestor, // conflicting child content node
|
||||
(ComponentExtensionNode)Ancestors[i + 1])); // Enclosing component of conflicting child content node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Components.Razor
|
||||
{
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// We don't support 'complex' content for components (mixed C# and markup) right now.
|
||||
// It's not clear yet if Blazor will have a good scenario to use these constructs.
|
||||
//
|
||||
// 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
|
||||
{
|
||||
// Run before other Blazor passes
|
||||
public override int Order => -1000;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var nodes = documentNode.FindDescendantNodes<TagHelperIntermediateNode>();
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
ProcessAttributes(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessAttributes(TagHelperIntermediateNode node)
|
||||
{
|
||||
for (var i = node.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode)
|
||||
{
|
||||
if (TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
|
||||
{
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
|
||||
propertyNode,
|
||||
propertyNode.AttributeName));
|
||||
node.Children.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode)
|
||||
{
|
||||
if (TrySimplifyContent(htmlNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
|
||||
{
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
|
||||
htmlNode,
|
||||
htmlNode.AttributeName));
|
||||
node.Children.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TrySimplifyContent(IntermediateNode node)
|
||||
{
|
||||
if (node.Children.Count == 1 &&
|
||||
node.Children[0] is HtmlAttributeIntermediateNode htmlNode &&
|
||||
htmlNode.Children.Count > 1)
|
||||
{
|
||||
// This case can be hit for a 'string' attribute
|
||||
return true;
|
||||
}
|
||||
else if (node.Children.Count == 1 &&
|
||||
node.Children[0] is CSharpExpressionIntermediateNode cSharpNode &&
|
||||
cSharpNode.Children.Count > 1)
|
||||
{
|
||||
// This case can be hit when the attribute has an explicit @ inside, which
|
||||
// 'escapes' any special sugar we provide for codegen.
|
||||
//
|
||||
// There's a special case here for explicit expressions. See https://github.com/aspnet/Razor/issues/2203
|
||||
// handling this case as a tactical matter since it's important for lambdas.
|
||||
if (cSharpNode.Children.Count == 3 &&
|
||||
cSharpNode.Children[0] is IntermediateToken token0 &&
|
||||
cSharpNode.Children[2] is IntermediateToken token2 &&
|
||||
token0.Content == "(" &&
|
||||
token2.Content == ")")
|
||||
{
|
||||
cSharpNode.Children.RemoveAt(2);
|
||||
cSharpNode.Children.RemoveAt(0);
|
||||
|
||||
// We were able to simplify it, all good.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (node.Children.Count == 1 &&
|
||||
node.Children[0] is CSharpCodeIntermediateNode cSharpCodeNode)
|
||||
{
|
||||
// This is the case when an attribute contains a code block @{ ... }
|
||||
// We don't support this.
|
||||
return true;
|
||||
}
|
||||
else if (node.Children.Count > 1)
|
||||
{
|
||||
// This is the common case for 'mixed' content
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,131 +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.Components.Razor
|
||||
{
|
||||
internal class ComponentAttributeExtensionNode : ExtensionIntermediateNode
|
||||
{
|
||||
public ComponentAttributeExtensionNode()
|
||||
{
|
||||
}
|
||||
|
||||
public ComponentAttributeExtensionNode(TagHelperHtmlAttributeIntermediateNode attributeNode)
|
||||
{
|
||||
if (attributeNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributeNode));
|
||||
}
|
||||
|
||||
AttributeName = attributeNode.AttributeName;
|
||||
AttributeStructure = attributeNode.AttributeStructure;
|
||||
Source = attributeNode.Source;
|
||||
|
||||
for (var i = 0; i < attributeNode.Children.Count; i++)
|
||||
{
|
||||
Children.Add(attributeNode.Children[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < attributeNode.Diagnostics.Count; i++)
|
||||
{
|
||||
Diagnostics.Add(attributeNode.Diagnostics[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public ComponentAttributeExtensionNode(TagHelperPropertyIntermediateNode propertyNode)
|
||||
{
|
||||
if (propertyNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyNode));
|
||||
}
|
||||
|
||||
AttributeName = propertyNode.AttributeName;
|
||||
AttributeStructure = propertyNode.AttributeStructure;
|
||||
BoundAttribute = propertyNode.BoundAttribute;
|
||||
PropertyName = propertyNode.BoundAttribute.GetPropertyName();
|
||||
Source = propertyNode.Source;
|
||||
TagHelper = propertyNode.TagHelper;
|
||||
TypeName = propertyNode.BoundAttribute.IsWeaklyTyped() ? null : propertyNode.BoundAttribute.TypeName;
|
||||
|
||||
for (var i = 0; i < propertyNode.Children.Count; i++)
|
||||
{
|
||||
Children.Add(propertyNode.Children[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < propertyNode.Diagnostics.Count; i++)
|
||||
{
|
||||
Diagnostics.Add(propertyNode.Diagnostics[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public ComponentAttributeExtensionNode(ComponentAttributeExtensionNode attributeNode)
|
||||
{
|
||||
if (attributeNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributeNode));
|
||||
}
|
||||
|
||||
AttributeName = attributeNode.AttributeName;
|
||||
AttributeStructure = attributeNode.AttributeStructure;
|
||||
BoundAttribute = attributeNode.BoundAttribute;
|
||||
PropertyName = attributeNode.BoundAttribute.GetPropertyName();
|
||||
Source = attributeNode.Source;
|
||||
TagHelper = attributeNode.TagHelper;
|
||||
TypeName = attributeNode.BoundAttribute.IsWeaklyTyped() ? null : attributeNode.BoundAttribute.TypeName;
|
||||
|
||||
for (var i = 0; i < attributeNode.Children.Count; i++)
|
||||
{
|
||||
Children.Add(attributeNode.Children[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < attributeNode.Diagnostics.Count; i++)
|
||||
{
|
||||
Diagnostics.Add(attributeNode.Diagnostics[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||
|
||||
public string AttributeName { get; set; }
|
||||
|
||||
public AttributeStructure AttributeStructure { get; set; }
|
||||
|
||||
public BoundAttributeDescriptor BoundAttribute { get; set; }
|
||||
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
public TagHelperDescriptor TagHelper { get; set; }
|
||||
|
||||
public string TypeName { get; set; }
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<ComponentAttributeExtensionNode>(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));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteComponentAttribute(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class ComponentChildContentIntermediateNode : ExtensionIntermediateNode
|
||||
{
|
||||
public string AttributeName => BoundAttribute?.Name ?? ComponentsApi.RenderTreeBuilder.ChildContent;
|
||||
|
||||
public BoundAttributeDescriptor BoundAttribute { get; set; }
|
||||
|
||||
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||
|
||||
public bool IsParameterized => BoundAttribute?.IsParameterizedChildContentProperty() ?? false;
|
||||
|
||||
public string ParameterName { get; set; }
|
||||
|
||||
public string TypeName { get; set; }
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<ComponentChildContentIntermediateNode>(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));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteComponentChildContent(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,208 +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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="DocumentClassifierPassBase"/> that recognizes Blazor components.
|
||||
/// </summary>
|
||||
public class ComponentDocumentClassifierPass : DocumentClassifierPassBase, IRazorDocumentClassifierPass
|
||||
{
|
||||
/// <summary>
|
||||
/// The component document kind.
|
||||
/// </summary>
|
||||
public static readonly string ComponentDocumentKind = "Blazor.Component";
|
||||
|
||||
private static readonly object BuildRenderTreeBaseCallAnnotation = new object();
|
||||
|
||||
private static readonly char[] PathSeparators = new char[] { '/', '\\' };
|
||||
|
||||
private static readonly char[] NamespaceSeparators = new char[] { '.' };
|
||||
|
||||
/// <summary>
|
||||
/// The base namespace.
|
||||
/// </summary>
|
||||
// This is a fallback value and will only be used if we can't compute
|
||||
// a reasonable namespace.
|
||||
public string BaseNamespace { get; set; } = "__BlazorGenerated";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to mangle class names.
|
||||
///
|
||||
/// Set to true in the IDE so we can generated mangled class names. This is needed
|
||||
/// to avoid conflicts between generated design-time code and the code in the editor.
|
||||
///
|
||||
/// A better workaround for this would be to create a singlefilegenerator that overrides
|
||||
/// the codegen process when a document is open, but this is more involved, so hacking
|
||||
/// it for now.
|
||||
/// </summary>
|
||||
public bool MangleClassNames { get; set; } = false;
|
||||
|
||||
internal static bool IsBuildRenderTreeBaseCall(CSharpCodeIntermediateNode node)
|
||||
=> node.Annotations[BuildRenderTreeBaseCallAnnotation] != null;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string DocumentKind => ComponentDocumentKind;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
// Treat everything as a component by default if Blazor is part of the configuration.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDocumentStructureCreated(
|
||||
RazorCodeDocument codeDocument,
|
||||
NamespaceDeclarationIntermediateNode @namespace,
|
||||
ClassDeclarationIntermediateNode @class,
|
||||
MethodDeclarationIntermediateNode method)
|
||||
{
|
||||
if (!TryComputeNamespaceAndClass(
|
||||
codeDocument.Source.FilePath,
|
||||
codeDocument.Source.RelativePath,
|
||||
out var computedNamespace,
|
||||
out var computedClass))
|
||||
{
|
||||
// If we can't compute a nice namespace (no relative path) then just generate something
|
||||
// mangled.
|
||||
computedNamespace = BaseNamespace;
|
||||
computedClass = CSharpIdentifier.GetClassNameFromPath(codeDocument.Source.FilePath) ?? "__BlazorComponent";
|
||||
}
|
||||
|
||||
if (MangleClassNames)
|
||||
{
|
||||
computedClass = "__" + computedClass;
|
||||
}
|
||||
|
||||
@namespace.Content = computedNamespace;
|
||||
|
||||
@class.BaseType = ComponentsApi.ComponentBase.FullTypeName;
|
||||
@class.ClassName = computedClass;
|
||||
@class.Modifiers.Clear();
|
||||
@class.Modifiers.Add("public");
|
||||
|
||||
var documentNode = codeDocument.GetDocumentIntermediateNode();
|
||||
var typeParamReferences = documentNode.FindDirectiveReferences(TypeParamDirective.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.ReturnType = "void";
|
||||
method.MethodName = ComponentsApi.ComponentBase.BuildRenderTree;
|
||||
method.Modifiers.Clear();
|
||||
method.Modifiers.Add("protected");
|
||||
method.Modifiers.Add("override");
|
||||
|
||||
method.Parameters.Clear();
|
||||
method.Parameters.Add(new MethodParameter()
|
||||
{
|
||||
ParameterName = "builder",
|
||||
TypeName = ComponentsApi.RenderTreeBuilder.FullTypeName,
|
||||
});
|
||||
|
||||
// We need to call the 'base' method as the first statement.
|
||||
var callBase = new CSharpCodeIntermediateNode();
|
||||
callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true);
|
||||
callBase.Children.Add(new IntermediateToken
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $"base.{ComponentsApi.ComponentBase.BuildRenderTree}(builder);"
|
||||
});
|
||||
method.Children.Insert(0, callBase);
|
||||
}
|
||||
|
||||
// In general documents will have a relative path (relative to the project root).
|
||||
// We can only really compute a nice class/namespace when we know a relative path.
|
||||
//
|
||||
// However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't
|
||||
// set up correctly.
|
||||
private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class)
|
||||
{
|
||||
if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length)
|
||||
{
|
||||
@namespace = null;
|
||||
@class = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try and infer a namespace from the project directory. We don't yet have the ability to pass
|
||||
// the namespace through from the project.
|
||||
var trimLength = relativePath.Length + (relativePath.StartsWith("/") ? 0 : 1);
|
||||
var baseDirectory = filePath.Substring(0, filePath.Length - trimLength);
|
||||
|
||||
var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators);
|
||||
var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1);
|
||||
if (string.IsNullOrEmpty(baseNamespace))
|
||||
{
|
||||
@namespace = null;
|
||||
@class = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
// Sanitize the base namespace, but leave the dots.
|
||||
var segments = baseNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
builder.Append(CSharpIdentifier.SanitizeClassName(segments[0]));
|
||||
for (var i = 1; i < segments.Length; i++)
|
||||
{
|
||||
builder.Append('.');
|
||||
builder.Append(CSharpIdentifier.SanitizeClassName(segments[i]));
|
||||
}
|
||||
|
||||
segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// Skip the last segment because it's the FileName.
|
||||
for (var i = 0; i < segments.Length - 1; i++)
|
||||
{
|
||||
builder.Append('.');
|
||||
builder.Append(CSharpIdentifier.SanitizeClassName(segments[i]));
|
||||
}
|
||||
|
||||
@namespace = builder.ToString();
|
||||
@class = CSharpIdentifier.SanitizeClassName(Path.GetFileNameWithoutExtension(relativePath));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Workaround
|
||||
// This is a workaround for the fact that the base class doesn't provide good support
|
||||
// for replacing the IntermediateNodeWriter when building the code target.
|
||||
void IRazorDocumentClassifierPass.Execute(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
base.Execute(codeDocument, documentNode);
|
||||
documentNode.Target = new BlazorCodeTarget(documentNode.Options, _targetExtensions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
var feature = Engine.Features.OfType<IRazorTargetExtensionFeature>();
|
||||
_targetExtensions = feature.FirstOrDefault()?.TargetExtensions.ToArray() ?? EmptyExtensionArray;
|
||||
}
|
||||
|
||||
private static readonly ICodeTargetExtension[] EmptyExtensionArray = new ICodeTargetExtension[0];
|
||||
private ICodeTargetExtension[] _targetExtensions;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,533 +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 AngleSharp;
|
||||
using AngleSharp.Extensions;
|
||||
using AngleSharp.Html;
|
||||
using AngleSharp.Parser.Html;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// Rewrites the standard IR to a format more suitable for Blazor
|
||||
//
|
||||
// HTML nodes are rewritten to contain more structure, instead of treating HTML as opaque content
|
||||
// it is structured into element/component nodes, and attribute nodes.
|
||||
internal class ComponentDocumentRewritePass : IntermediateNodePassBase, IRazorDocumentClassifierPass
|
||||
{
|
||||
// Per the HTML spec, the following elements are inherently self-closing
|
||||
// For example, <img> is the same as <img /> (and therefore it cannot contain descendants)
|
||||
public static readonly HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr",
|
||||
};
|
||||
|
||||
// Run as soon as possible after the Component document classifier
|
||||
public override int Order => ComponentDocumentClassifierPass.DefaultFeatureOrder + 1;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
if (documentNode.DocumentKind != ComponentDocumentClassifierPass.ComponentDocumentKind)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var visitor = new RewriteWalker(codeDocument.Source);
|
||||
visitor.Visit(documentNode);
|
||||
}
|
||||
|
||||
// Visits nodes then rewrites them using a post-order traversal. The result is that the tree
|
||||
// is rewritten bottom up.
|
||||
//
|
||||
// This relies on a few invariants Razor already provides for correctness.
|
||||
// - Tag Helpers are the only real nesting construct
|
||||
// - Tag Helpers require properly nested HTML inside their body
|
||||
//
|
||||
// This means that when we find a 'container' for HTML content, we have the guarantee
|
||||
// that the content is properly nested, except at the top level of scope. And since the top
|
||||
// level isn't nested inside anything, we can't introduce any errors due to misunderstanding
|
||||
// the structure.
|
||||
private class RewriteWalker : IntermediateNodeWalker
|
||||
{
|
||||
private readonly RazorSourceDocument _source;
|
||||
|
||||
public RewriteWalker(RazorSourceDocument source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
public override void VisitDefault(IntermediateNode node)
|
||||
{
|
||||
var foundHtml = false;
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var child = node.Children[i];
|
||||
Visit(child);
|
||||
|
||||
if (child is HtmlContentIntermediateNode)
|
||||
{
|
||||
foundHtml = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundHtml)
|
||||
{
|
||||
RewriteChildren(_source, node);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
|
||||
{
|
||||
// Don't rewrite inside of attributes
|
||||
}
|
||||
|
||||
public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
|
||||
{
|
||||
// Don't rewrite inside of attributes
|
||||
}
|
||||
|
||||
public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
// Don't rewrite inside of attributes
|
||||
}
|
||||
|
||||
private void RewriteChildren(RazorSourceDocument source, IntermediateNode node)
|
||||
{
|
||||
// We expect all of the immediate children of a node (together) to comprise
|
||||
// a well-formed tree of elements and components.
|
||||
var stack = new Stack<IntermediateNode>();
|
||||
stack.Push(node);
|
||||
|
||||
// Make a copy, we will clear and rebuild the child collection of this node.
|
||||
var children = node.Children.ToArray();
|
||||
node.Children.Clear();
|
||||
|
||||
// Due to the way Anglesharp parses HTML (tags at a time) we need to keep track of some state.
|
||||
// This handles cases like:
|
||||
//
|
||||
// <foo bar="17" baz="@baz" />
|
||||
//
|
||||
// This will lower like:
|
||||
//
|
||||
// HtmlContent <foo bar="17"
|
||||
// HtmlAttribute baz=" - "
|
||||
// CSharpAttributeValue baz
|
||||
// HtmlContent />
|
||||
//
|
||||
// We need to consume HTML until we see the 'end tag' for <foo /> and then we can
|
||||
// the attributes from the parsed HTML and the CSharpAttribute value.
|
||||
var parser = new HtmlParser(source);
|
||||
var attributes = new List<HtmlAttributeIntermediateNode>();
|
||||
|
||||
for (var i = 0; i < children.Length; i++)
|
||||
{
|
||||
if (children[i] is HtmlContentIntermediateNode htmlNode)
|
||||
{
|
||||
parser.Push(htmlNode);
|
||||
var tokens = parser.Get();
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
// We have to call this before get. Anglesharp doesn't return the start position
|
||||
// of tokens.
|
||||
var start = parser.GetCurrentLocation();
|
||||
|
||||
// We have to set the Location explicitly otherwise we would need to include
|
||||
// the token in every call to the parser.
|
||||
parser.SetLocation(token);
|
||||
|
||||
var end = parser.GetCurrentLocation();
|
||||
|
||||
if (token.Type == HtmlTokenType.EndOfFile)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
switch (token.Type)
|
||||
{
|
||||
case HtmlTokenType.Doctype:
|
||||
{
|
||||
// DocType isn't meaningful in Blazor. We don't process them in the runtime
|
||||
// it wouldn't really mean much anyway since we build a DOM directly rather
|
||||
// than letting the user-agent parse the document.
|
||||
//
|
||||
// For now, <!DOCTYPE html> and similar things will just be skipped by the compiler
|
||||
// unless we come up with something more meaningful to do.
|
||||
break;
|
||||
}
|
||||
|
||||
case HtmlTokenType.Character:
|
||||
{
|
||||
// Text content
|
||||
var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
|
||||
stack.Peek().Children.Add(new HtmlContentIntermediateNode()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = token.Data,
|
||||
Kind = TokenKind.Html,
|
||||
Source = span,
|
||||
}
|
||||
},
|
||||
Source = span,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case HtmlTokenType.StartTag:
|
||||
{
|
||||
var tag = token.AsTag();
|
||||
|
||||
if (token.Type == HtmlTokenType.StartTag)
|
||||
{
|
||||
var elementNode = new HtmlElementIntermediateNode()
|
||||
{
|
||||
TagName = parser.GetTagNameOriginalCasing(tag),
|
||||
Source = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex),
|
||||
};
|
||||
|
||||
stack.Peek().Children.Add(elementNode);
|
||||
stack.Push(elementNode);
|
||||
|
||||
for (var j = 0; j < tag.Attributes.Count; j++)
|
||||
{
|
||||
// Unfortunately Anglesharp doesn't provide positions for attributes
|
||||
// so we can't record the spans here.
|
||||
var attribute = tag.Attributes[j];
|
||||
stack.Peek().Children.Add(CreateAttributeNode(attribute));
|
||||
}
|
||||
|
||||
for (var j = 0; j < attributes.Count; j++)
|
||||
{
|
||||
stack.Peek().Children.Add(attributes[j]);
|
||||
}
|
||||
attributes.Clear();
|
||||
}
|
||||
|
||||
if (tag.IsSelfClosing || VoidElements.Contains(tag.Data))
|
||||
{
|
||||
// We can't possibly hit an error here since we just added an element node.
|
||||
stack.Pop();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HtmlTokenType.EndTag:
|
||||
{
|
||||
var tag = token.AsTag();
|
||||
|
||||
var popped = stack.Pop();
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
// If we managed to 'bottom out' the stack then we have an unbalanced end tag.
|
||||
// Put back the current node so we don't crash.
|
||||
stack.Push(popped);
|
||||
|
||||
var tagName = parser.GetTagNameOriginalCasing(tag);
|
||||
var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
|
||||
|
||||
var diagnostic = VoidElements.Contains(tagName)
|
||||
? BlazorDiagnosticFactory.Create_UnexpectedClosingTagForVoidElement(span, tagName)
|
||||
: BlazorDiagnosticFactory.Create_UnexpectedClosingTag(span, tagName);
|
||||
popped.Children.Add(new HtmlElementIntermediateNode()
|
||||
{
|
||||
Diagnostics =
|
||||
{
|
||||
diagnostic,
|
||||
},
|
||||
TagName = tagName,
|
||||
Source = span,
|
||||
});
|
||||
}
|
||||
else if (!string.Equals(tag.Name, ((HtmlElementIntermediateNode)popped).TagName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
|
||||
var diagnostic = BlazorDiagnosticFactory.Create_MismatchedClosingTag(span, ((HtmlElementIntermediateNode)popped).TagName, token.Data);
|
||||
popped.Diagnostics.Add(diagnostic);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Happy path.
|
||||
//
|
||||
// We need to compute a new source span because when we found the start tag before we knew
|
||||
// the end position of the tag.
|
||||
var length = end.AbsoluteIndex - popped.Source.Value.AbsoluteIndex;
|
||||
popped.Source = new SourceSpan(
|
||||
popped.Source.Value.FilePath,
|
||||
popped.Source.Value.AbsoluteIndex,
|
||||
popped.Source.Value.LineIndex,
|
||||
popped.Source.Value.CharacterIndex,
|
||||
length);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HtmlTokenType.Comment:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidCastException($"Unsupported token type: {token.Type.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (children[i] is HtmlAttributeIntermediateNode htmlAttribute)
|
||||
{
|
||||
// Buffer the attribute for now, it will get written out as part of a tag.
|
||||
attributes.Add(htmlAttribute);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not HTML, or already rewritten.
|
||||
stack.Peek().Children.Add(children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var extraContent = parser.GetUnparsedContent();
|
||||
if (!string.IsNullOrEmpty(extraContent))
|
||||
{
|
||||
// extra HTML - almost certainly invalid because it couldn't be parsed.
|
||||
var start = parser.GetCurrentLocation();
|
||||
var end = parser.GetCurrentLocation(extraContent.Length);
|
||||
var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
|
||||
stack.Peek().Children.Add(new HtmlContentIntermediateNode()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = extraContent,
|
||||
Kind = TokenKind.Html,
|
||||
Source = span,
|
||||
}
|
||||
},
|
||||
Diagnostics =
|
||||
{
|
||||
BlazorDiagnosticFactory.Create_InvalidHtmlContent(span, extraContent),
|
||||
},
|
||||
Source = span,
|
||||
});
|
||||
}
|
||||
|
||||
while (stack.Count > 1)
|
||||
{
|
||||
// not balanced
|
||||
var popped = (HtmlElementIntermediateNode)stack.Pop();
|
||||
var diagnostic = BlazorDiagnosticFactory.Create_UnclosedTag(popped.Source, popped.TagName);
|
||||
popped.Diagnostics.Add(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HtmlAttributeIntermediateNode CreateAttributeNode(KeyValuePair<string, string> attribute)
|
||||
{
|
||||
return new HtmlAttributeIntermediateNode()
|
||||
{
|
||||
AttributeName = attribute.Key,
|
||||
Children =
|
||||
{
|
||||
new HtmlAttributeValueIntermediateNode()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new IntermediateToken()
|
||||
{
|
||||
Kind = TokenKind.Html,
|
||||
Content = attribute.Value,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetHtmlContent(HtmlContentIntermediateNode node)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var token = node.Children[i] as IntermediateToken;
|
||||
if (token != null && token.IsHtml)
|
||||
{
|
||||
builder.Append(token.Content);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
private class HtmlParser
|
||||
{
|
||||
private readonly RazorSourceDocument _source;
|
||||
|
||||
// Tracks the offsets between the start of _content and then original source document.
|
||||
private List<(int offset, int sourceOffset)> _offsets;
|
||||
private TextSource _textSource;
|
||||
private int _position;
|
||||
private string _content;
|
||||
|
||||
public HtmlParser(RazorSourceDocument source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
public void Push(HtmlContentIntermediateNode node)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
var offsets = new List<(int offset, int sourceOffset)>();
|
||||
|
||||
if (_content != null && _position < _content.Length)
|
||||
{
|
||||
offsets.Add((0, _offsets[0].sourceOffset + _position));
|
||||
builder.Append(_content, _position, _content.Length - _position);
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var token = node.Children[i] as IntermediateToken;
|
||||
if (token != null && token.IsHtml)
|
||||
{
|
||||
offsets.Add((builder.Length, token.Source.Value.AbsoluteIndex));
|
||||
builder.Append(token.Content);
|
||||
}
|
||||
}
|
||||
|
||||
_content = builder.ToString();
|
||||
_offsets = offsets;
|
||||
_textSource = new TextSource(_content);
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public string GetUnparsedContent()
|
||||
{
|
||||
return _position >= _content.Length ? string.Empty : _content.Substring(_position);
|
||||
}
|
||||
|
||||
public IEnumerable<HtmlToken> Get()
|
||||
{
|
||||
if (_textSource == null)
|
||||
{
|
||||
throw new InvalidOperationException("You need to call Push first.");
|
||||
}
|
||||
|
||||
// This will decode any HTML entities into their textual equivalent.
|
||||
//
|
||||
// This is OK because an HtmlContent node is used with document.createTextNode
|
||||
// in the DOM, which can accept content that has decoded entities.
|
||||
//
|
||||
// In the event that we merge HtmlContent into an HtmlBlock, we need to
|
||||
// re-encode the entities. That's done in the HtmlBlock pass.
|
||||
var tokens = _textSource.Tokenize(HtmlEntityService.Resolver);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public void SetLocation(HtmlToken token)
|
||||
{
|
||||
// The tokenizer will advance to the end when you have an unclosed tag.
|
||||
// We don't want this, we want to resume before the unclosed tag.
|
||||
if (token.Type != HtmlTokenType.EndOfFile)
|
||||
{
|
||||
_position = _textSource.Index;
|
||||
}
|
||||
}
|
||||
|
||||
public SourceLocation GetCurrentLocation(int offset = 0)
|
||||
{
|
||||
var absoluteIndex = GetAbsoluteIndex(_position + offset);
|
||||
|
||||
int lineIndex = -1;
|
||||
int columnIndex = -1;
|
||||
var remaining = absoluteIndex;
|
||||
for (var i = 0; i < _source.Lines.Count; i++)
|
||||
{
|
||||
var lineLength = _source.Lines.GetLineLength(i);
|
||||
if (lineLength > remaining)
|
||||
{
|
||||
lineIndex = i;
|
||||
columnIndex = remaining;
|
||||
break;
|
||||
}
|
||||
|
||||
remaining -= lineLength;
|
||||
}
|
||||
|
||||
return new SourceLocation(_source.FilePath, absoluteIndex, lineIndex, columnIndex);
|
||||
}
|
||||
|
||||
public SourceSpan GetSpan(HtmlToken token)
|
||||
{
|
||||
var absoluteIndex = GetAbsoluteIndex(token.Position.Position);
|
||||
|
||||
int lineIndex = -1;
|
||||
int columnIndex = -1;
|
||||
var remaining = absoluteIndex;
|
||||
for (var i = 0; i < _source.Lines.Count; i++)
|
||||
{
|
||||
var lineLength = _source.Lines.GetLineLength(i);
|
||||
if (lineLength > remaining)
|
||||
{
|
||||
lineIndex = i;
|
||||
columnIndex = remaining;
|
||||
break;
|
||||
}
|
||||
|
||||
remaining -= lineLength;
|
||||
}
|
||||
|
||||
var length = GetAbsoluteIndex(_position) - absoluteIndex;
|
||||
return new SourceSpan(_source.FilePath, absoluteIndex, lineIndex, columnIndex, length);
|
||||
}
|
||||
|
||||
private int GetAbsoluteIndex(int contentIndex)
|
||||
{
|
||||
for (var i = _offsets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_offsets[i].offset <= contentIndex)
|
||||
{
|
||||
return _offsets[i].sourceOffset + (contentIndex - _offsets[i].offset);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Unexpected index value.");
|
||||
}
|
||||
|
||||
// Anglesharp canonicalizes the case of tags, we want what the user typed.
|
||||
public string GetTagNameOriginalCasing(HtmlTagToken tag)
|
||||
{
|
||||
var offset = tag.Type == HtmlTokenType.EndTag ? 1 : 0; // For end tags, skip the '/'
|
||||
return tag.Name;
|
||||
}
|
||||
|
||||
private string DebuggerDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
return "Content={}";
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("Content=");
|
||||
builder.Append("{");
|
||||
builder.Append(_content.Substring(0, Math.Min(_position, _content.Length)));
|
||||
builder.Append("|");
|
||||
builder.Append(_content.Substring(Math.Min(_position, _content.Length)));
|
||||
builder.Append("}");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +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 System.Text;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class ComponentExtensionNode : ExtensionIntermediateNode
|
||||
{
|
||||
public IEnumerable<ComponentAttributeExtensionNode> Attributes => Children.OfType<ComponentAttributeExtensionNode>();
|
||||
|
||||
public IEnumerable<RefExtensionNode> Captures => Children.OfType<RefExtensionNode>();
|
||||
|
||||
public IEnumerable<ComponentChildContentIntermediateNode> ChildContents => Children.OfType<ComponentChildContentIntermediateNode>();
|
||||
|
||||
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||
|
||||
public TagHelperDescriptor Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child content parameter name (null if unset) that was applied at the component level.
|
||||
/// </summary>
|
||||
public string ChildContentParameterName { get; set; }
|
||||
|
||||
public IEnumerable<ComponentTypeArgumentExtensionNode> TypeArguments => Children.OfType<ComponentTypeArgumentExtensionNode>();
|
||||
|
||||
public string TagName { get; set; }
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<ComponentExtensionNode>(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));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteComponent(context, this);
|
||||
}
|
||||
|
||||
private string DebuggerDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("Component: ");
|
||||
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("=\"...\"");
|
||||
}
|
||||
|
||||
foreach (var typeArgument in TypeArguments)
|
||||
{
|
||||
builder.Append(" ");
|
||||
builder.Append(typeArgument.TypeParameterName);
|
||||
builder.Append("=\"...\"");
|
||||
}
|
||||
|
||||
builder.Append(">");
|
||||
builder.Append(ChildContents.Any() ? "..." : string.Empty);
|
||||
builder.Append("</");
|
||||
builder.Append(TagName);
|
||||
builder.Append(">");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,490 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class ComponentLoweringPass : IntermediateNodePassBase, 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)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
// Nothing to do, bail. We can't function without the standard structure.
|
||||
return;
|
||||
}
|
||||
|
||||
// For each component *usage* we need to rewrite the tag helper node to map to the relevant component
|
||||
// APIs.
|
||||
var references = documentNode.FindDescendantReferences<TagHelperIntermediateNode>();
|
||||
for (var i = 0; i < references.Count; i++)
|
||||
{
|
||||
var reference = references[i];
|
||||
var node = (TagHelperIntermediateNode)reference.Node;
|
||||
|
||||
var count = 0;
|
||||
for (var j = 0; j < node.TagHelpers.Count; j++)
|
||||
{
|
||||
if (node.TagHelpers[j].IsComponentTagHelper())
|
||||
{
|
||||
// Only allow a single component tag helper per element. If there are multiple, we'll just consider
|
||||
// the first one and ignore the others.
|
||||
if (count++ > 1)
|
||||
{
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count >= 1)
|
||||
{
|
||||
reference.Replace(RewriteAsComponent(node, node.TagHelpers.First(t => t.IsComponentTagHelper())));
|
||||
}
|
||||
else if (node.TagHelpers.Any(t => t.IsChildContentTagHelper()))
|
||||
{
|
||||
// Ignore, this will be handled when we rewrite the parent.
|
||||
}
|
||||
else
|
||||
{
|
||||
reference.Replace(RewriteAsElement(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentExtensionNode RewriteAsComponent(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
|
||||
{
|
||||
var component = new ComponentExtensionNode()
|
||||
{
|
||||
Component = tagHelper,
|
||||
Source = node.Source,
|
||||
TagName = node.TagName,
|
||||
TypeName = tagHelper.GetTypeName(),
|
||||
};
|
||||
|
||||
for (var i = 0; i < node.Diagnostics.Count; i++)
|
||||
{
|
||||
component.Diagnostics.Add(node.Diagnostics[i]);
|
||||
}
|
||||
|
||||
var visitor = new ComponentRewriteVisitor(component);
|
||||
visitor.Visit(node);
|
||||
|
||||
// Fixup the parameter names of child content elements. We can't do this during the rewrite
|
||||
// because we see the nodes in the wrong order.
|
||||
foreach (var childContent in component.ChildContents)
|
||||
{
|
||||
childContent.ParameterName = childContent.ParameterName ?? component.ChildContentParameterName ?? BlazorMetadata.ChildContent.DefaultParameterName;
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
private HtmlElementIntermediateNode RewriteAsElement(TagHelperIntermediateNode node)
|
||||
{
|
||||
var result = new HtmlElementIntermediateNode()
|
||||
{
|
||||
Source = node.Source,
|
||||
TagName = node.TagName,
|
||||
};
|
||||
|
||||
for (var i = 0; i < node.Diagnostics.Count; i++)
|
||||
{
|
||||
result.Diagnostics.Add(node.Diagnostics[i]);
|
||||
}
|
||||
|
||||
var visitor = new ElementRewriteVisitor(result.Children);
|
||||
visitor.Visit(node);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class ComponentRewriteVisitor : IntermediateNodeWalker
|
||||
{
|
||||
private readonly ComponentExtensionNode _component;
|
||||
private readonly IntermediateNodeCollection _children;
|
||||
|
||||
public ComponentRewriteVisitor(ComponentExtensionNode component)
|
||||
{
|
||||
_component = component;
|
||||
_children = component.Children;
|
||||
}
|
||||
|
||||
public override void VisitTagHelper(TagHelperIntermediateNode node)
|
||||
{
|
||||
// Visit children, we're replacing this node.
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
|
||||
public override void VisitTagHelperBody(TagHelperBodyIntermediateNode node)
|
||||
{
|
||||
// Wrap the component's children in a ChildContent node if we have some significant
|
||||
// content.
|
||||
if (node.Children.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get a single HTML content node containing only whitespace,
|
||||
// then this is probably a tag that looks like '<MyComponent> </MyComponent>
|
||||
//
|
||||
// We don't want to create a child content for this case, because it can conflict
|
||||
// with a child content that's set via an attribute. We don't want the formatting
|
||||
// of insignificant whitespace to be annoying when setting attributes directly.
|
||||
if (node.Children.Count == 1 && IsIgnorableWhitespace(node.Children[0]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// From here we fork and behave differently based on whether the component's child content is
|
||||
// implicit or explicit.
|
||||
//
|
||||
// Explicit child content will look like: <MyComponent><ChildContent><div>...</div></ChildContent></MyComponent>
|
||||
// compared with implicit: <MyComponent><div></div></MyComponent>
|
||||
//
|
||||
// Using implicit child content:
|
||||
// 1. All content is grouped into a single child content lambda, and assigned to the property 'ChildContent'
|
||||
//
|
||||
// Using explicit child content:
|
||||
// 1. All content must be contained within 'child content' elements that are direct children
|
||||
// 2. Whitespace outside of 'child content' elements will be ignored (not an error)
|
||||
// 3. Non-whitespace outside of 'child content' elements will cause an error
|
||||
// 4. All 'child content' elements must match parameters on the component (exception for ChildContent,
|
||||
// which is always allowed.
|
||||
// 5. Each 'child content' element will generate its own lambda, and be assigned to the property
|
||||
// that matches the element name.
|
||||
if (!node.Children.OfType<TagHelperIntermediateNode>().Any(t => t.TagHelpers.Any(th => th.IsChildContentTagHelper())))
|
||||
{
|
||||
// This node has implicit child content. It may or may not have an attribute that matches.
|
||||
var attribute = _component.Component.BoundAttributes
|
||||
.Where(a => string.Equals(a.Name, ComponentsApi.RenderTreeBuilder.ChildContent, StringComparison.Ordinal))
|
||||
.FirstOrDefault();
|
||||
_children.Add(RewriteChildContent(attribute, node.Source, node.Children));
|
||||
return;
|
||||
}
|
||||
|
||||
// OK this node has explicit child content, we can rewrite it by visiting each node
|
||||
// in sequence, since we:
|
||||
// a) need to rewrite each child content element
|
||||
// b) any significant content outside of a child content is an error
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var child = node.Children[i];
|
||||
if (IsIgnorableWhitespace(child))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child is TagHelperIntermediateNode tagHelperNode &&
|
||||
tagHelperNode.TagHelpers.Any(th => th.IsChildContentTagHelper()))
|
||||
{
|
||||
// This is a child content element
|
||||
var attribute = _component.Component.BoundAttributes
|
||||
.Where(a => string.Equals(a.Name, tagHelperNode.TagName, StringComparison.Ordinal))
|
||||
.FirstOrDefault();
|
||||
_children.Add(RewriteChildContent(attribute, child.Source, child.Children));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we get here then this is significant content inside a component with explicit child content.
|
||||
child.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentMixedWithExplicitChildContent(child.Source, _component));
|
||||
_children.Add(child);
|
||||
}
|
||||
|
||||
bool IsIgnorableWhitespace(IntermediateNode n)
|
||||
{
|
||||
if (n is HtmlContentIntermediateNode html &&
|
||||
html.Children.Count == 1 &&
|
||||
html.Children[0] is IntermediateToken token &&
|
||||
string.IsNullOrWhiteSpace(token.Content))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentChildContentIntermediateNode RewriteChildContent(BoundAttributeDescriptor attribute, SourceSpan? source, IntermediateNodeCollection children)
|
||||
{
|
||||
var childContent = new ComponentChildContentIntermediateNode()
|
||||
{
|
||||
BoundAttribute = attribute,
|
||||
Source = source,
|
||||
TypeName = attribute?.TypeName ?? ComponentsApi.RenderFragment.FullTypeName,
|
||||
};
|
||||
|
||||
// There are two cases here:
|
||||
// 1. Implicit child content - the children will be non-taghelper nodes, just accept them
|
||||
// 2. Explicit child content - the children will be various tag helper nodes, that need special processing.
|
||||
for (var i = 0; i < children.Count; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (child is TagHelperBodyIntermediateNode body)
|
||||
{
|
||||
// The body is all of the content we want to render, the rest of the children will
|
||||
// be the attributes.
|
||||
for (var j = 0; j < body.Children.Count; j++)
|
||||
{
|
||||
childContent.Children.Add(body.Children[j]);
|
||||
}
|
||||
}
|
||||
else if (child is TagHelperPropertyIntermediateNode property)
|
||||
{
|
||||
if (property.BoundAttribute.IsChildContentParameterNameProperty())
|
||||
{
|
||||
// Check for each child content with a parameter name, that the parameter name is specified
|
||||
// with literal text. For instance, the following is not allowed and should generate a diagnostic.
|
||||
//
|
||||
// <MyComponent><ChildContent Context="@Foo()">...</ChildContent></MyComponent>
|
||||
if (TryGetAttributeStringContent(property, out var parameterName))
|
||||
{
|
||||
childContent.ParameterName = parameterName;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The parameter name is invalid.
|
||||
childContent.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidParameter(property.Source, property.AttributeName, attribute.Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is an unrecognized attribute, this is possible if you try to do something like put 'ref' on a child content.
|
||||
childContent.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidAttribute(property.Source, property.AttributeName, attribute.Name));
|
||||
}
|
||||
else if (child is TagHelperHtmlAttributeIntermediateNode a)
|
||||
{
|
||||
// This is an HTML attribute on a child content.
|
||||
childContent.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidAttribute(a.Source, a.AttributeName, attribute.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is some other kind of node (likely an implicit child content)
|
||||
childContent.Children.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
return childContent;
|
||||
}
|
||||
|
||||
private bool TryGetAttributeStringContent(TagHelperPropertyIntermediateNode property, out string content)
|
||||
{
|
||||
// The success path looks like - a single HTML Attribute Value node with tokens
|
||||
if (property.Children.Count == 1 &&
|
||||
property.Children[0] is HtmlContentIntermediateNode html)
|
||||
{
|
||||
content = string.Join(string.Empty, html.Children.OfType<IntermediateToken>().Select(n => n.Content));
|
||||
return true;
|
||||
}
|
||||
|
||||
content = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
|
||||
{
|
||||
var attribute = new ComponentAttributeExtensionNode(node);
|
||||
_children.Add(attribute);
|
||||
|
||||
// Since we don't support complex content, we can rewrite the inside of this
|
||||
// node to the rather simpler form that property nodes usually have.
|
||||
for (var i = 0; i < attribute.Children.Count; i++)
|
||||
{
|
||||
if (attribute.Children[i] is HtmlAttributeValueIntermediateNode htmlValue)
|
||||
{
|
||||
attribute.Children[i] = new HtmlContentIntermediateNode()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
htmlValue.Children.Single(),
|
||||
},
|
||||
Source = htmlValue.Source,
|
||||
};
|
||||
}
|
||||
else if (attribute.Children[i] is CSharpExpressionAttributeValueIntermediateNode expressionValue)
|
||||
{
|
||||
attribute.Children[i] = new CSharpExpressionIntermediateNode()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
expressionValue.Children.Single(),
|
||||
},
|
||||
Source = expressionValue.Source,
|
||||
};
|
||||
}
|
||||
else if (attribute.Children[i] is CSharpCodeAttributeValueIntermediateNode codeValue)
|
||||
{
|
||||
attribute.Children[i] = new CSharpExpressionIntermediateNode()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
codeValue.Children.Single(),
|
||||
},
|
||||
Source = codeValue.Source,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
// Each 'tag helper property' belongs to a specific tag helper. We want to handle
|
||||
// the cases for components, but leave others alone. This allows our other passes
|
||||
// to handle those cases.
|
||||
if (!node.TagHelper.IsComponentTagHelper())
|
||||
{
|
||||
_children.Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// Another special case here - this might be a type argument. These don't represent 'real' parameters
|
||||
// that get passed to the component, it needs special code generation support.
|
||||
if (node.TagHelper.IsGenericTypedComponent() && node.BoundAttribute.IsTypeParameterProperty())
|
||||
{
|
||||
_children.Add(new ComponentTypeArgumentExtensionNode(node));
|
||||
return;
|
||||
}
|
||||
|
||||
// Another special case here -- this might be a 'Context' parameter, which specifies the name
|
||||
// for lambda parameter for parameterized child content
|
||||
if (node.BoundAttribute.IsChildContentParameterNameProperty())
|
||||
{
|
||||
// Check for each child content with a parameter name, that the parameter name is specified
|
||||
// with literal text. For instance, the following is not allowed and should generate a diagnostic.
|
||||
//
|
||||
// <MyComponent Context="@Foo()">...</MyComponent>
|
||||
if (TryGetAttributeStringContent(node, out var parameterName))
|
||||
{
|
||||
_component.ChildContentParameterName = parameterName;
|
||||
return;
|
||||
}
|
||||
|
||||
// The parameter name is invalid.
|
||||
_component.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidParameterOnComponent(node.Source, node.AttributeName, _component.TagName));
|
||||
return;
|
||||
}
|
||||
|
||||
_children.Add(new ComponentAttributeExtensionNode(node));
|
||||
}
|
||||
|
||||
public override void VisitDefault(IntermediateNode node)
|
||||
{
|
||||
_children.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private class ElementRewriteVisitor : IntermediateNodeWalker
|
||||
{
|
||||
private readonly IntermediateNodeCollection _children;
|
||||
|
||||
public ElementRewriteVisitor(IntermediateNodeCollection children)
|
||||
{
|
||||
_children = children;
|
||||
}
|
||||
|
||||
public override void VisitTagHelper(TagHelperIntermediateNode node)
|
||||
{
|
||||
// Visit children, we're replacing this node.
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
Visit(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitTagHelperBody(TagHelperBodyIntermediateNode node)
|
||||
{
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
_children.Add(node.Children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
|
||||
{
|
||||
var attribute = new HtmlAttributeIntermediateNode()
|
||||
{
|
||||
AttributeName = node.AttributeName,
|
||||
Source = node.Source,
|
||||
};
|
||||
_children.Add(attribute);
|
||||
|
||||
for (var i = 0; i < node.Diagnostics.Count; i++)
|
||||
{
|
||||
attribute.Diagnostics.Add(node.Diagnostics[i]);
|
||||
}
|
||||
|
||||
switch (node.AttributeStructure)
|
||||
{
|
||||
case AttributeStructure.Minimized:
|
||||
|
||||
attribute.Prefix = node.AttributeName;
|
||||
attribute.Suffix = string.Empty;
|
||||
break;
|
||||
|
||||
case AttributeStructure.NoQuotes:
|
||||
case AttributeStructure.SingleQuotes:
|
||||
case AttributeStructure.DoubleQuotes:
|
||||
|
||||
// We're ignoring attribute structure here for simplicity, it doesn't effect us.
|
||||
attribute.Prefix = node.AttributeName + "=\"";
|
||||
attribute.Suffix = "\"";
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
attribute.Children.Add(RewriteAttributeContent(node.Children[i]));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
IntermediateNode RewriteAttributeContent(IntermediateNode content)
|
||||
{
|
||||
if (content is HtmlContentIntermediateNode html)
|
||||
{
|
||||
var value = new HtmlAttributeValueIntermediateNode()
|
||||
{
|
||||
Source = content.Source,
|
||||
};
|
||||
|
||||
for (var i = 0; i < html.Children.Count; i++)
|
||||
{
|
||||
value.Children.Add(html.Children[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < html.Diagnostics.Count; i++)
|
||||
{
|
||||
value.Diagnostics.Add(html.Diagnostics[i]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
// Each 'tag helper property' belongs to a specific tag helper. We want to handle
|
||||
// the cases for components, but leave others alone. This allows our other passes
|
||||
// to handle those cases.
|
||||
_children.Add(node.TagHelper.IsComponentTagHelper() ? (IntermediateNode)new ComponentAttributeExtensionNode(node) : node);
|
||||
}
|
||||
|
||||
public override void VisitDefault(IntermediateNode node)
|
||||
{
|
||||
_children.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,505 +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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
|
||||
{
|
||||
private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
|
||||
SymbolDisplayFormat.FullyQualifiedFormat
|
||||
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
|
||||
.WithMiscellaneousOptions(SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions & (~SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
|
||||
|
||||
private static MethodInfo WithMetadataImportOptionsMethodInfo =
|
||||
typeof(CSharpCompilationOptions)
|
||||
.GetMethod("WithMetadataImportOptions", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
public bool IncludeDocumentation { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var compilation = context.GetCompilation();
|
||||
if (compilation == null)
|
||||
{
|
||||
// No compilation, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to see private members too
|
||||
compilation = WithMetadataImportOptionsAll(compilation);
|
||||
|
||||
var symbols = BlazorSymbols.Create(compilation);
|
||||
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = new ComponentTypeVisitor(symbols, types);
|
||||
|
||||
// Visit the primary output of this compilation, as well as all references.
|
||||
visitor.Visit(compilation.Assembly);
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
// We ignore .netmodules here - there really isn't a case where they are used by user code
|
||||
// even though the Roslyn APIs all support them.
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
visitor.Visit(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < types.Count; i++)
|
||||
{
|
||||
var type = types[i];
|
||||
var descriptor = CreateDescriptor(symbols, type);
|
||||
context.Results.Add(descriptor);
|
||||
|
||||
foreach (var childContent in descriptor.GetChildContentProperties())
|
||||
{
|
||||
// Synthesize a separate tag helper for each child content property that's declared.
|
||||
context.Results.Add(CreateChildContentDescriptor(symbols, descriptor, childContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Compilation WithMetadataImportOptionsAll(Compilation compilation)
|
||||
{
|
||||
var newCompilationOptions = (CSharpCompilationOptions)WithMetadataImportOptionsMethodInfo
|
||||
.Invoke(compilation.Options, new object[] { /* All */ (byte)2 });
|
||||
return compilation.WithOptions(newCompilationOptions);
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateDescriptor(BlazorSymbols symbols, INamedTypeSymbol type)
|
||||
{
|
||||
var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
var assemblyName = type.ContainingAssembly.Identity.Name;
|
||||
|
||||
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Component.TagHelperKind, typeName, assemblyName);
|
||||
builder.SetTypeName(typeName);
|
||||
|
||||
// This opts out this 'component' tag helper for any processing that's specific to the default
|
||||
// Razor ITagHelper runtime.
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Component.RuntimeName;
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
builder.Metadata[BlazorMetadata.Component.GenericTypedKey] = bool.TrueString;
|
||||
|
||||
for (var i = 0; i < type.TypeArguments.Length; i++)
|
||||
{
|
||||
var typeParameter = type.TypeArguments[i] as ITypeParameterSymbol;
|
||||
if (typeParameter != null)
|
||||
{
|
||||
CreateTypeParameterProperty(builder, typeParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xml = type.GetDocumentationCommentXml();
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
builder.Documentation = xml;
|
||||
}
|
||||
|
||||
// Components have very simple matching rules. The type name (short) matches the tag name.
|
||||
builder.TagMatchingRule(r => r.TagName = type.Name);
|
||||
|
||||
foreach (var property in GetProperties(symbols, type))
|
||||
{
|
||||
if (property.kind == PropertyKind.Ignored)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CreateProperty(builder, property.property, property.kind);
|
||||
}
|
||||
|
||||
if (builder.BoundAttributes.Any(a => a.IsParameterizedChildContentProperty()) &&
|
||||
!builder.BoundAttributes.Any(a => string.Equals(a.Name, BlazorMetadata.ChildContent.ParameterAttributeName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// If we have any parameterized child content parameters, synthesize a 'Context' parameter to be
|
||||
// able to set the variable name (for all child content). If the developer defined a 'Context' parameter
|
||||
// already, then theirs wins.
|
||||
CreateContextParameter(builder, childContentName: null);
|
||||
}
|
||||
|
||||
var descriptor = builder.Build();
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private void CreateProperty(TagHelperDescriptorBuilder builder, IPropertySymbol property, PropertyKind kind)
|
||||
{
|
||||
builder.BindAttribute(pb =>
|
||||
{
|
||||
pb.Name = property.Name;
|
||||
pb.TypeName = property.Type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
pb.SetPropertyName(property.Name);
|
||||
|
||||
if (kind == PropertyKind.Enum)
|
||||
{
|
||||
pb.IsEnum = true;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.ChildContent)
|
||||
{
|
||||
pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString);
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Delegate)
|
||||
{
|
||||
pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString);
|
||||
}
|
||||
|
||||
if (HasTypeParameter(property.Type))
|
||||
{
|
||||
pb.Metadata.Add(BlazorMetadata.Component.GenericTypedKey, bool.TrueString);
|
||||
}
|
||||
|
||||
var xml = property.GetDocumentationCommentXml();
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
pb.Documentation = xml;
|
||||
}
|
||||
});
|
||||
|
||||
bool HasTypeParameter(ITypeSymbol type)
|
||||
{
|
||||
if (type is ITypeParameterSymbol)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// We need to check for cases like:
|
||||
// [Parameter] List<T> MyProperty { get; set; }
|
||||
// AND
|
||||
// [Parameter] List<string> MyProperty { get; set; }
|
||||
//
|
||||
// We need to inspect the type arguments to tell the difference between a property that
|
||||
// uses the containing class' type parameter(s) and a vanilla usage of generic types like
|
||||
// List<> and Dictionary<,>
|
||||
//
|
||||
// Since we need to handle cases like RenderFragment<List<T>>, this check must be recursive.
|
||||
if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
|
||||
{
|
||||
var typeArguments = namedType.TypeArguments;
|
||||
for (var i = 0; i < typeArguments.Length; i++)
|
||||
{
|
||||
if (HasTypeParameter(typeArguments[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Another case to handle - if the type being inspected is a nested type
|
||||
// inside a generic containing class. The common usage for this would be a case
|
||||
// where a generic templated component defines a 'context' nested class.
|
||||
if (namedType.ContainingType != null && HasTypeParameter(namedType.ContainingType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateTypeParameterProperty(TagHelperDescriptorBuilder builder, ITypeSymbol typeParameter)
|
||||
{
|
||||
builder.BindAttribute(pb =>
|
||||
{
|
||||
pb.DisplayName = typeParameter.Name;
|
||||
pb.Name = typeParameter.Name;
|
||||
pb.TypeName = typeof(Type).FullName;
|
||||
pb.SetPropertyName(typeParameter.Name);
|
||||
|
||||
pb.Metadata[BlazorMetadata.Component.TypeParameterKey] = bool.TrueString;
|
||||
|
||||
pb.Documentation = string.Format(Resources.ComponentTypeParameter_Documentation, typeParameter.Name, builder.Name);
|
||||
});
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateChildContentDescriptor(BlazorSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute)
|
||||
{
|
||||
var typeName = component.GetTypeName() + "." + attribute.Name;
|
||||
var assemblyName = component.AssemblyName;
|
||||
|
||||
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.ChildContent.TagHelperKind, typeName, assemblyName);
|
||||
builder.SetTypeName(typeName);
|
||||
|
||||
// This opts out this 'component' tag helper for any processing that's specific to the default
|
||||
// Razor ITagHelper runtime.
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.ChildContent.RuntimeName;
|
||||
|
||||
// Opt out of processing as a component. We'll process this specially as part of the component's body.
|
||||
builder.Metadata[BlazorMetadata.SpecialKindKey] = BlazorMetadata.ChildContent.TagHelperKind;
|
||||
|
||||
var xml = attribute.Documentation;
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
builder.Documentation = xml;
|
||||
}
|
||||
|
||||
// Child content matches the property name, but only as a direct child of the component.
|
||||
builder.TagMatchingRule(r =>
|
||||
{
|
||||
r.TagName = attribute.Name;
|
||||
r.ParentTag = component.TagMatchingRules.First().TagName;
|
||||
});
|
||||
|
||||
if (attribute.IsParameterizedChildContentProperty())
|
||||
{
|
||||
// For child content attributes with a parameter, synthesize an attribute that allows you to name
|
||||
// the parameter.
|
||||
CreateContextParameter(builder, attribute.Name);
|
||||
}
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private void CreateContextParameter(TagHelperDescriptorBuilder builder, string childContentName)
|
||||
{
|
||||
builder.BindAttribute(b =>
|
||||
{
|
||||
b.Name = BlazorMetadata.ChildContent.ParameterAttributeName;
|
||||
b.TypeName = typeof(string).FullName;
|
||||
b.Metadata.Add(BlazorMetadata.Component.ChildContentParameterNameKey, bool.TrueString);
|
||||
|
||||
if (childContentName == null)
|
||||
{
|
||||
b.Documentation = Resources.ChildContentParameterName_TopLevelDocumentation;
|
||||
}
|
||||
else
|
||||
{
|
||||
b.Documentation = string.Format(Resources.ChildContentParameterName_Documentation, childContentName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Does a walk up the inheritance chain to determine the set of parameters by using
|
||||
// a dictionary keyed on property name.
|
||||
//
|
||||
// We consider parameters to be defined by properties satisfying all of the following:
|
||||
// - are visible (not shadowed)
|
||||
// - have the [Parameter] attribute
|
||||
// - have a setter, even if private
|
||||
// - are not indexers
|
||||
private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetProperties(BlazorSymbols symbols, INamedTypeSymbol type)
|
||||
{
|
||||
var properties = new Dictionary<string, (IPropertySymbol, PropertyKind)>(StringComparer.Ordinal);
|
||||
do
|
||||
{
|
||||
if (type == symbols.ComponentBase)
|
||||
{
|
||||
// The ComponentBase base class doesn't have any [Parameter].
|
||||
// Bail out now to avoid walking through its many members, plus the members
|
||||
// of the System.Object base class.
|
||||
break;
|
||||
}
|
||||
|
||||
var members = type.GetMembers();
|
||||
for (var i = 0; i < members.Length; i++)
|
||||
{
|
||||
var property = members[i] as IPropertySymbol;
|
||||
if (property == null)
|
||||
{
|
||||
// Not a property
|
||||
continue;
|
||||
}
|
||||
|
||||
if (properties.ContainsKey(property.Name))
|
||||
{
|
||||
// Not visible
|
||||
continue;
|
||||
}
|
||||
|
||||
var kind = PropertyKind.Default;
|
||||
if (property.Parameters.Length != 0)
|
||||
{
|
||||
// Indexer
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (property.SetMethod == null)
|
||||
{
|
||||
// No setter
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (property.IsStatic)
|
||||
{
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (!property.GetAttributes().Any(a => a.AttributeClass == symbols.ParameterAttribute))
|
||||
{
|
||||
// Does not have [Parameter]
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Enum)
|
||||
{
|
||||
kind = PropertyKind.Enum;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default && property.Type == symbols.RenderFragment)
|
||||
{
|
||||
kind = PropertyKind.ChildContent;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default &&
|
||||
property.Type is INamedTypeSymbol namedType &&
|
||||
namedType.IsGenericType &&
|
||||
namedType.ConstructedFrom == symbols.RenderFragmentOfT)
|
||||
{
|
||||
kind = PropertyKind.ChildContent;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Delegate)
|
||||
{
|
||||
kind = PropertyKind.Delegate;
|
||||
}
|
||||
|
||||
properties.Add(property.Name, (property, kind));
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
while (type != null);
|
||||
|
||||
return properties.Values;
|
||||
}
|
||||
|
||||
private enum PropertyKind
|
||||
{
|
||||
Ignored,
|
||||
Default,
|
||||
Enum,
|
||||
ChildContent,
|
||||
Delegate,
|
||||
}
|
||||
|
||||
private class BlazorSymbols
|
||||
{
|
||||
public static BlazorSymbols Create(Compilation compilation)
|
||||
{
|
||||
var symbols = new BlazorSymbols();
|
||||
symbols.ComponentBase = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName);
|
||||
if (symbols.ComponentBase == null)
|
||||
{
|
||||
// No definition for ComponentBase, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
symbols.IComponent = compilation.GetTypeByMetadataName(ComponentsApi.IComponent.MetadataName);
|
||||
if (symbols.IComponent == null)
|
||||
{
|
||||
// No definition for IComponent, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
symbols.ParameterAttribute = compilation.GetTypeByMetadataName(ComponentsApi.ParameterAttribute.MetadataName);
|
||||
if (symbols.ParameterAttribute == null)
|
||||
{
|
||||
// No definition for [Parameter], nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
symbols.RenderFragment = compilation.GetTypeByMetadataName(ComponentsApi.RenderFragment.MetadataName);
|
||||
if (symbols.RenderFragment == null)
|
||||
{
|
||||
// No definition for RenderFragment, nothing to do.
|
||||
}
|
||||
|
||||
symbols.RenderFragmentOfT = compilation.GetTypeByMetadataName(ComponentsApi.RenderFragmentOfT.MetadataName);
|
||||
if (symbols.RenderFragmentOfT == null)
|
||||
{
|
||||
// No definition for RenderFragment, nothing to do.
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
private BlazorSymbols()
|
||||
{
|
||||
}
|
||||
|
||||
public INamedTypeSymbol ComponentBase { get; private set; }
|
||||
|
||||
public INamedTypeSymbol IComponent { get; private set; }
|
||||
|
||||
public INamedTypeSymbol ParameterAttribute { get; private set; }
|
||||
|
||||
public INamedTypeSymbol RenderFragment { get; private set; }
|
||||
|
||||
public INamedTypeSymbol RenderFragmentOfT { get; private set; }
|
||||
}
|
||||
|
||||
private class ComponentTypeVisitor : SymbolVisitor
|
||||
{
|
||||
private readonly BlazorSymbols _symbols;
|
||||
private readonly List<INamedTypeSymbol> _results;
|
||||
|
||||
public ComponentTypeVisitor(BlazorSymbols symbols, List<INamedTypeSymbol> results)
|
||||
{
|
||||
_symbols = symbols;
|
||||
_results = results;
|
||||
}
|
||||
|
||||
public override void VisitNamedType(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (IsComponent(symbol))
|
||||
{
|
||||
_results.Add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitNamespace(INamespaceSymbol symbol)
|
||||
{
|
||||
foreach (var member in symbol.GetMembers())
|
||||
{
|
||||
Visit(member);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitAssembly(IAssemblySymbol symbol)
|
||||
{
|
||||
// This as a simple yet high-value optimization that excludes the vast majority of
|
||||
// assemblies that (by definition) can't contain a component.
|
||||
if (symbol.Name != null && !symbol.Name.StartsWith("System.", StringComparison.Ordinal))
|
||||
{
|
||||
Visit(symbol.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsComponent(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (_symbols == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
symbol.DeclaredAccessibility == Accessibility.Public &&
|
||||
!symbol.IsAbstract &&
|
||||
symbol.AllInterfaces.Contains(_symbols.IComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +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.Components.Razor
|
||||
{
|
||||
internal class ComponentTypeArgumentExtensionNode : ExtensionIntermediateNode
|
||||
{
|
||||
public ComponentTypeArgumentExtensionNode(TagHelperPropertyIntermediateNode propertyNode)
|
||||
{
|
||||
if (propertyNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyNode));
|
||||
}
|
||||
|
||||
BoundAttribute = propertyNode.BoundAttribute;
|
||||
Source = propertyNode.Source;
|
||||
TagHelper = propertyNode.TagHelper;
|
||||
|
||||
for (var i = 0; i < propertyNode.Children.Count; i++)
|
||||
{
|
||||
Children.Add(propertyNode.Children[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < propertyNode.Diagnostics.Count; i++)
|
||||
{
|
||||
Diagnostics.Add(propertyNode.Diagnostics[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||
|
||||
public BoundAttributeDescriptor BoundAttribute { get; set; }
|
||||
|
||||
public string TypeParameterName => BoundAttribute.Name;
|
||||
|
||||
public TagHelperDescriptor TagHelper { get; set; }
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<ComponentTypeArgumentExtensionNode>(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));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteComponentTypeArgument(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +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 Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a type-inference thunk that is used by the generated component code.
|
||||
/// </summary>
|
||||
internal class ComponentTypeInferenceMethodIntermediateNode : ExtensionIntermediateNode
|
||||
{
|
||||
public Dictionary<string, GenericTypeNameRewriter.Binding> Bindings { get; set; }
|
||||
|
||||
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component usage linked to this type inference method.
|
||||
/// </summary>
|
||||
public ComponentExtensionNode Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full type name of the generated class containing this method.
|
||||
/// </summary>
|
||||
public string FullTypeName { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the generated method.
|
||||
/// </summary>
|
||||
public string MethodName { get; set; }
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<ComponentTypeInferenceMethodIntermediateNode>(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));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteComponentTypeInferenceMethod(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
|
||||
{
|
||||
internal static class DocumentIntermediateNodeExtensions
|
||||
{
|
||||
public static IReadOnlyList<IntermediateNodeReference> FindDescendantReferences<TNode>(this DocumentIntermediateNode document)
|
||||
where TNode : IntermediateNode
|
||||
{
|
||||
if (document == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(document));
|
||||
}
|
||||
|
||||
var visitor = new ReferenceVisitor<TNode>();
|
||||
visitor.Visit(document);
|
||||
return visitor.References;
|
||||
}
|
||||
|
||||
private class ReferenceVisitor<TNode> : IntermediateNodeWalker
|
||||
where TNode : IntermediateNode
|
||||
{
|
||||
public List<IntermediateNodeReference> References = new List<IntermediateNodeReference>();
|
||||
|
||||
public override void VisitDefault(IntermediateNode node)
|
||||
{
|
||||
base.VisitDefault(node);
|
||||
|
||||
// Use a post-order traversal because references are used to replace nodes, and thus
|
||||
// change the parent nodes.
|
||||
//
|
||||
// This ensures that we always operate on the leaf nodes first.
|
||||
if (node is TNode)
|
||||
{
|
||||
References.Add(new IntermediateNodeReference(Parent, node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +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.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class EliminateMethodBodyPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
// Run early in the optimization phase
|
||||
public override int Order => Int32.MinValue;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
if (codeDocument == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
if (documentNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentNode));
|
||||
}
|
||||
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
if (method == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
method.Children.Clear();
|
||||
|
||||
// After we clear all of the method body there might be some unused fields, which can be
|
||||
// blocking if compiling with warnings as errors. Suppress this warning so that it doesn't
|
||||
// get annoying in VS.
|
||||
documentNode.Children.Insert(documentNode.Children.IndexOf(documentNode.FindPrimaryNamespace()), new CSharpCodeIntermediateNode()
|
||||
{
|
||||
Children =
|
||||
{
|
||||
// Field is assigned but never used
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = "#pragma warning disable 0414" + Environment.NewLine,
|
||||
Kind = TokenKind.CSharp,
|
||||
},
|
||||
|
||||
// Field is never assigned
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = "#pragma warning disable 0649" + Environment.NewLine,
|
||||
Kind = TokenKind.CSharp,
|
||||
},
|
||||
|
||||
// Field is never used
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = "#pragma warning disable 0169" + Environment.NewLine,
|
||||
Kind = TokenKind.CSharp,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,211 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class EventHandlerLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
public override int Order => 50;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
// Nothing to do, bail. We can't function without the standard structure.
|
||||
return;
|
||||
}
|
||||
|
||||
// For each event handler *usage* we need to rewrite the tag helper node to map to basic constructs.
|
||||
// Each usage will be represented by a tag helper property that is a descendant of either
|
||||
// a component or element.
|
||||
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
|
||||
|
||||
var parents = new HashSet<IntermediateNode>();
|
||||
for (var i = 0; i < references.Count; i++)
|
||||
{
|
||||
parents.Add(references[i].Parent);
|
||||
}
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
ProcessDuplicates(parent);
|
||||
}
|
||||
|
||||
for (var i = 0; i < references.Count; i++)
|
||||
{
|
||||
var reference = references[i];
|
||||
var node = (TagHelperPropertyIntermediateNode)reference.Node;
|
||||
|
||||
if (!reference.Parent.Children.Contains(node))
|
||||
{
|
||||
// This node was removed as a duplicate, skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.TagHelper.IsEventHandlerTagHelper())
|
||||
{
|
||||
reference.Replace(RewriteUsage(reference.Parent, node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDuplicates(IntermediateNode parent)
|
||||
{
|
||||
// Reverse order because we will remove nodes.
|
||||
//
|
||||
// Each 'property' node could be duplicated if there are multiple tag helpers that match that
|
||||
// particular attribute. This is likely to happen when a component also defines something like
|
||||
// OnClick. We want to remove the 'onclick' and let it fall back to be handled by the component.
|
||||
for (var i = parent.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var eventHandler = parent.Children[i] as TagHelperPropertyIntermediateNode;
|
||||
if (eventHandler != null &&
|
||||
eventHandler.TagHelper != null &&
|
||||
eventHandler.TagHelper.IsEventHandlerTagHelper())
|
||||
{
|
||||
for (var j = 0; j < parent.Children.Count; j++)
|
||||
{
|
||||
var componentAttribute = parent.Children[j] as ComponentAttributeExtensionNode;
|
||||
if (componentAttribute != null &&
|
||||
componentAttribute.TagHelper != null &&
|
||||
componentAttribute.TagHelper.IsComponentTagHelper() &&
|
||||
componentAttribute.AttributeName == eventHandler.AttributeName)
|
||||
{
|
||||
// Found a duplicate - remove the 'fallback' in favor of the component's own handling.
|
||||
parent.Children.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we still have duplicates at this point then they are genuine conflicts.
|
||||
var duplicates = parent.Children
|
||||
.OfType<TagHelperPropertyIntermediateNode>()
|
||||
.Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false)
|
||||
.GroupBy(p => p.AttributeName)
|
||||
.Where(g => g.Count() > 1);
|
||||
|
||||
foreach (var duplicate in duplicates)
|
||||
{
|
||||
parent.Diagnostics.Add(BlazorDiagnosticFactory.CreateEventHandler_Duplicates(
|
||||
parent.Source,
|
||||
duplicate.Key,
|
||||
duplicate.ToArray()));
|
||||
foreach (var property in duplicate)
|
||||
{
|
||||
parent.Children.Remove(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
var original = GetAttributeContent(node);
|
||||
if (original.Count == 0)
|
||||
{
|
||||
// This can happen in error cases, the parser will already have flagged this
|
||||
// as an error, so ignore it.
|
||||
return node;
|
||||
}
|
||||
|
||||
// Now rewrite the content of the value node to look like:
|
||||
//
|
||||
// BindMethods.GetEventHandlerValue<TDelegate>(<code>)
|
||||
//
|
||||
// This method is overloaded on string and TDelegate, which means that it will put the code in the
|
||||
// correct context for intellisense when typing in the attribute.
|
||||
var eventArgsType = node.TagHelper.GetEventArgsType();
|
||||
var tokens = new List<IntermediateToken>()
|
||||
{
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = $"{ComponentsApi.BindMethods.GetEventHandlerValue}<{eventArgsType}>(",
|
||||
Kind = TokenKind.CSharp
|
||||
},
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = $")",
|
||||
Kind = TokenKind.CSharp
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < original.Count; i++)
|
||||
{
|
||||
tokens.Insert(i + 1, original[i]);
|
||||
}
|
||||
|
||||
if (parent is HtmlElementIntermediateNode)
|
||||
{
|
||||
var result = new HtmlAttributeIntermediateNode()
|
||||
{
|
||||
AttributeName = node.AttributeName,
|
||||
Source = node.Source,
|
||||
|
||||
Prefix = node.AttributeName + "=\"",
|
||||
Suffix = "\"",
|
||||
};
|
||||
|
||||
for (var i = 0; i < node.Diagnostics.Count; i++)
|
||||
{
|
||||
result.Diagnostics.Add(node.Diagnostics[i]);
|
||||
}
|
||||
|
||||
result.Children.Add(new CSharpExpressionAttributeValueIntermediateNode());
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
result.Children[0].Children.Add(tokens[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new ComponentAttributeExtensionNode(node);
|
||||
|
||||
result.Children.Clear();
|
||||
result.Children.Add(new CSharpExpressionIntermediateNode());
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
result.Children[0].Children.Add(tokens[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<IntermediateToken> GetAttributeContent(TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
|
||||
if (template != null)
|
||||
{
|
||||
// See comments in TemplateDiagnosticPass
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_TemplateInvalidLocation(template.Source));
|
||||
return new[] { new IntermediateToken() { Kind = TokenKind.CSharp, Content = string.Empty, }, };
|
||||
}
|
||||
|
||||
if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlContentNode)
|
||||
{
|
||||
// This case can be hit for a 'string' attribute. We want to turn it into
|
||||
// an expression.
|
||||
var tokens = htmlContentNode.FindDescendantNodes<IntermediateToken>();
|
||||
|
||||
var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content.Replace("\"", "\\\""))) + "\"";
|
||||
return new[] { new IntermediateToken() { Content = content, Kind = TokenKind.CSharp, } };
|
||||
}
|
||||
else
|
||||
{
|
||||
return node.FindDescendantNodes<IntermediateToken>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,220 +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 Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class EventHandlerTagHelperDescriptorProvider : ITagHelperDescriptorProvider
|
||||
{
|
||||
public int Order { get; set; }
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var compilation = context.GetCompilation();
|
||||
if (compilation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bindMethods = compilation.GetTypeByMetadataName(ComponentsApi.BindMethods.FullTypeName);
|
||||
if (bindMethods == null)
|
||||
{
|
||||
// If we can't find BindMethods, then just bail. We won't be able to compile the
|
||||
// generated code anyway.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var eventHandlerData = GetEventHandlerData(compilation);
|
||||
|
||||
foreach (var tagHelper in CreateEventHandlerTagHelpers(eventHandlerData))
|
||||
{
|
||||
context.Results.Add(tagHelper);
|
||||
}
|
||||
}
|
||||
|
||||
private List<EventHandlerData> GetEventHandlerData(Compilation compilation)
|
||||
{
|
||||
var eventHandlerAttribute = compilation.GetTypeByMetadataName(ComponentsApi.EventHandlerAttribute.FullTypeName);
|
||||
if (eventHandlerAttribute == null)
|
||||
{
|
||||
// This won't likely happen, but just in case.
|
||||
return new List<EventHandlerData>();
|
||||
}
|
||||
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = new EventHandlerDataVisitor(types);
|
||||
|
||||
// Visit the primary output of this compilation, as well as all references.
|
||||
visitor.Visit(compilation.Assembly);
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
// We ignore .netmodules here - there really isn't a case where they are used by user code
|
||||
// even though the Roslyn APIs all support them.
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
visitor.Visit(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
var results = new List<EventHandlerData>();
|
||||
|
||||
for (var i = 0; i < types.Count; i++)
|
||||
{
|
||||
var type = types[i];
|
||||
var attributes = type.GetAttributes();
|
||||
|
||||
// Not handling duplicates here for now since we're the primary ones extending this.
|
||||
// If we see users adding to the set of event handler constructs we will want to add deduplication
|
||||
// and potentially diagnostics.
|
||||
for (var j = 0; j < attributes.Length; j++)
|
||||
{
|
||||
var attribute = attributes[j];
|
||||
|
||||
if (attribute.AttributeClass == eventHandlerAttribute)
|
||||
{
|
||||
results.Add(new EventHandlerData(
|
||||
type.ContainingAssembly.Name,
|
||||
type.ToDisplayString(),
|
||||
(string)attribute.ConstructorArguments[0].Value,
|
||||
(INamedTypeSymbol)attribute.ConstructorArguments[1].Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<TagHelperDescriptor> CreateEventHandlerTagHelpers(List<EventHandlerData> data)
|
||||
{
|
||||
var results = new List<TagHelperDescriptor>();
|
||||
|
||||
for (var i = 0; i < data.Count; i++)
|
||||
{
|
||||
var entry = data[i];
|
||||
|
||||
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.EventHandler.TagHelperKind, entry.Attribute, entry.Assembly);
|
||||
builder.Documentation = string.Format(
|
||||
Resources.EventHandlerTagHelper_Documentation,
|
||||
entry.Attribute,
|
||||
entry.EventArgsType.ToDisplayString());
|
||||
|
||||
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.EventHandler.TagHelperKind);
|
||||
builder.Metadata.Add(BlazorMetadata.EventHandler.EventArgsType, entry.EventArgsType.ToDisplayString());
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.EventHandler.RuntimeName;
|
||||
|
||||
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the tooltips.
|
||||
builder.SetTypeName(entry.TypeName);
|
||||
|
||||
builder.TagMatchingRule(rule =>
|
||||
{
|
||||
rule.TagName = "*";
|
||||
|
||||
rule.Attribute(a =>
|
||||
{
|
||||
a.Name = entry.Attribute;
|
||||
a.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.FullMatch;
|
||||
});
|
||||
});
|
||||
|
||||
builder.BindAttribute(a =>
|
||||
{
|
||||
a.Documentation = string.Format(
|
||||
Resources.EventHandlerTagHelper_Documentation,
|
||||
entry.Attribute,
|
||||
entry.EventArgsType.ToDisplayString());
|
||||
|
||||
a.Name = entry.Attribute;
|
||||
|
||||
// Use a string here so that we get HTML context by default.
|
||||
a.TypeName = typeof(string).FullName;
|
||||
|
||||
// But make this weakly typed (don't type check) - delegates have their own type-checking
|
||||
// logic that we don't want to interfere with.
|
||||
a.Metadata.Add(BlazorMetadata.Component.WeaklyTypedKey, bool.TrueString);
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the tooltips.
|
||||
a.SetPropertyName(entry.Attribute);
|
||||
});
|
||||
|
||||
results.Add(builder.Build());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private struct EventHandlerData
|
||||
{
|
||||
public EventHandlerData(
|
||||
string assembly,
|
||||
string typeName,
|
||||
string element,
|
||||
INamedTypeSymbol eventArgsType)
|
||||
{
|
||||
Assembly = assembly;
|
||||
TypeName = typeName;
|
||||
Attribute = element;
|
||||
EventArgsType = eventArgsType;
|
||||
}
|
||||
|
||||
public string Assembly { get; }
|
||||
|
||||
public string TypeName { get; }
|
||||
|
||||
public string Attribute { get; }
|
||||
|
||||
public INamedTypeSymbol EventArgsType { get; }
|
||||
}
|
||||
|
||||
private class EventHandlerDataVisitor : SymbolVisitor
|
||||
{
|
||||
private List<INamedTypeSymbol> _results;
|
||||
|
||||
public EventHandlerDataVisitor(List<INamedTypeSymbol> results)
|
||||
{
|
||||
_results = results;
|
||||
}
|
||||
|
||||
public override void VisitNamedType(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (symbol.Name == "EventHandlers" && symbol.DeclaredAccessibility == Accessibility.Public)
|
||||
{
|
||||
_results.Add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitNamespace(INamespaceSymbol symbol)
|
||||
{
|
||||
foreach (var member in symbol.GetMembers())
|
||||
{
|
||||
Visit(member);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitAssembly(IAssemblySymbol symbol)
|
||||
{
|
||||
// This as a simple yet high-value optimization that excludes the vast majority of
|
||||
// assemblies that (by definition) can't contain a component.
|
||||
if (symbol.Name != null && !symbol.Name.StartsWith("System.", StringComparison.Ordinal))
|
||||
{
|
||||
Visit(symbol.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,305 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// This pass:
|
||||
// 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
|
||||
{
|
||||
// Runs after components/eventhandlers/ref/bind/templates. We want to validate every component
|
||||
// and it's usage of ChildContent.
|
||||
public override int Order => 160;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(documentNode);
|
||||
}
|
||||
|
||||
|
||||
private class Visitor : IntermediateNodeWalker, IExtensionIntermediateNodeVisitor<ComponentExtensionNode>
|
||||
{
|
||||
// Incrementing ID for type inference method names
|
||||
private int _id;
|
||||
|
||||
public void VisitExtension(ComponentExtensionNode node)
|
||||
{
|
||||
if (node.Component.IsGenericTypedComponent())
|
||||
{
|
||||
// Not generic, ignore.
|
||||
Process(node);
|
||||
}
|
||||
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
|
||||
private void Process(ComponentExtensionNode node)
|
||||
{
|
||||
// First collect all of the information we have about each type parameter
|
||||
//
|
||||
// Listing all type parameters that exist
|
||||
var bindings = new Dictionary<string, GenericTypeNameRewriter.Binding>();
|
||||
foreach (var attribute in node.Component.GetTypeParameters())
|
||||
{
|
||||
bindings.Add(attribute.Name, new GenericTypeNameRewriter.Binding() { Attribute = attribute, });
|
||||
}
|
||||
|
||||
// Listing all type arguments that have been specified.
|
||||
var hasTypeArgumentSpecified = false;
|
||||
foreach (var typeArgumentNode in node.TypeArguments)
|
||||
{
|
||||
hasTypeArgumentSpecified = true;
|
||||
|
||||
var binding = bindings[typeArgumentNode.TypeParameterName];
|
||||
binding.Node = typeArgumentNode;
|
||||
binding.Content = GetContent(typeArgumentNode);
|
||||
}
|
||||
|
||||
if (hasTypeArgumentSpecified)
|
||||
{
|
||||
// OK this means that the developer has specified at least one type parameter.
|
||||
// Either they specified everything and its OK to rewrite, or its an error.
|
||||
if (ValidateTypeArguments(node, bindings))
|
||||
{
|
||||
RewriteTypeNames(new GenericTypeNameRewriter(bindings), node);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// OK if we get here that means that no type arguments were specified, so we will try to infer
|
||||
// the type.
|
||||
//
|
||||
// The actual inference is done by the C# compiler, we just emit an a method that represents the
|
||||
// use of this component.
|
||||
|
||||
// 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(new GlobalQualifiedTypeNameRewriter(bindings.Keys), node);
|
||||
|
||||
//
|
||||
// We need to verify that an argument was provided that 'covers' each type parameter.
|
||||
//
|
||||
// For example, consider a repeater where the generic type is the 'item' type, but the developer has
|
||||
// not set the items. We won't be able to do type inference on this and so it will just be nonsense.
|
||||
var attributes = node.Attributes.Select(a => a.BoundAttribute).Concat(node.ChildContents.Select(c => c.BoundAttribute));
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
// Will be null for attributes set on the component that don't match a declared component parameter
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we need to parse the type name and extract the generic parameters.
|
||||
//
|
||||
// Two cases;
|
||||
// 1. name is a simple identifier like TItem
|
||||
// 2. name contains type parameters like Dictionary<string, TItem>
|
||||
if (!attribute.IsGenericTypedProperty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parsed = SyntaxFactory.ParseTypeName(attribute.TypeName);
|
||||
if (parsed is IdentifierNameSyntax identifier)
|
||||
{
|
||||
bindings.Remove(identifier.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeParameters = parsed.DescendantNodesAndSelf().OfType<TypeArgumentListSyntax>().SelectMany(arg => arg.Arguments);
|
||||
foreach (var typeParameter in typeParameters)
|
||||
{
|
||||
bindings.Remove(typeParameter.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If any bindings remain then this means we would never be able to infer the arguments of this
|
||||
// component usage because the user hasn't set properties that include all of the types.
|
||||
if (bindings.Count > 0)
|
||||
{
|
||||
// However we still want to generate 'type inference' code because we want the errors to be as
|
||||
// helpful as possible. So let's substitute 'object' for all of those type parameters, and add
|
||||
// an error.
|
||||
RewriteTypeNames(new GenericTypeNameRewriter(bindings), node);
|
||||
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_GenericComponentTypeInferenceUnderspecified(node.Source, node, node.Component.GetTypeParameters()));
|
||||
}
|
||||
|
||||
// Next we need to generate a type inference 'method' node. This represents a method that we will codegen that
|
||||
// contains all of the operations on the render tree building. Calling a method to operate on the builder
|
||||
// will allow the C# compiler to perform type inference.
|
||||
var documentNode = (DocumentIntermediateNode)Ancestors[Ancestors.Count - 1];
|
||||
CreateTypeInferenceMethod(documentNode, node, bindings);
|
||||
}
|
||||
|
||||
private string GetContent(ComponentTypeArgumentExtensionNode node)
|
||||
{
|
||||
return string.Join(string.Empty, node.FindDescendantNodes<IntermediateToken>().Where(t => t.IsCSharp).Select(t => t.Content));
|
||||
}
|
||||
|
||||
private static bool ValidateTypeArguments(ComponentExtensionNode node, Dictionary<string, GenericTypeNameRewriter.Binding> bindings)
|
||||
{
|
||||
var missing = new List<BoundAttributeDescriptor>();
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
if (binding.Value.Node == null || string.IsNullOrWhiteSpace(binding.Value.Content))
|
||||
{
|
||||
missing.Add(binding.Value.Attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.Count > 0)
|
||||
{
|
||||
// We add our own error for this because its likely the user will see other errors due
|
||||
// to incorrect codegen without the types. Our errors message will pretty clearly indicate
|
||||
// what to do, whereas the other errors might be confusing.
|
||||
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_GenericComponentMissingTypeArgument(node.Source, node, missing));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RewriteTypeNames(CSharpSyntaxRewriter rewriter, ComponentExtensionNode node)
|
||||
{
|
||||
// Rewrite the component type name
|
||||
node.TypeName = RewriteTypeName(rewriter, node.TypeName);
|
||||
|
||||
foreach (var attribute in node.Attributes)
|
||||
{
|
||||
if (attribute.BoundAttribute?.IsGenericTypedProperty() ?? false && attribute.TypeName != null)
|
||||
{
|
||||
// If we know the type name, then replace any generic type parameter inside it with
|
||||
// the known types.
|
||||
attribute.TypeName = RewriteTypeName(rewriter, attribute.TypeName);
|
||||
}
|
||||
else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsDelegateProperty() ?? false))
|
||||
{
|
||||
// This is a weakly typed delegate, treat it as Action<object>
|
||||
attribute.TypeName = "System.Action<System.Object>";
|
||||
}
|
||||
else if (attribute.TypeName == null)
|
||||
{
|
||||
// This is a weakly typed attribute, treat it as System.Object
|
||||
attribute.TypeName = "System.Object";
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var capture in node.Captures)
|
||||
{
|
||||
if (capture.IsComponentCapture && capture.ComponentCaptureTypeName != null)
|
||||
{
|
||||
capture.ComponentCaptureTypeName = RewriteTypeName(rewriter, capture.ComponentCaptureTypeName);
|
||||
}
|
||||
else if (capture.IsComponentCapture)
|
||||
{
|
||||
capture.ComponentCaptureTypeName = "System.Object";
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var childContent in node.ChildContents)
|
||||
{
|
||||
if (childContent.BoundAttribute?.IsGenericTypedProperty() ?? false && childContent.TypeName != null)
|
||||
{
|
||||
// If we know the type name, then replace any generic type parameter inside it with
|
||||
// the known types.
|
||||
childContent.TypeName = RewriteTypeName(rewriter, childContent.TypeName);
|
||||
}
|
||||
else if (childContent.IsParameterized)
|
||||
{
|
||||
// This is a weakly typed parameterized child content, treat it as RenderFragment<object>
|
||||
childContent.TypeName = ComponentsApi.RenderFragment.FullTypeName + "<System.Object>";
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a weakly typed child content, treat it as RenderFragment
|
||||
childContent.TypeName = ComponentsApi.RenderFragment.FullTypeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string RewriteTypeName(CSharpSyntaxRewriter rewriter, string typeName)
|
||||
{
|
||||
var parsed = SyntaxFactory.ParseTypeName(typeName);
|
||||
var rewritten = (TypeSyntax)rewriter.Visit(parsed);
|
||||
return rewritten.ToFullString();
|
||||
}
|
||||
|
||||
private void CreateTypeInferenceMethod(
|
||||
DocumentIntermediateNode documentNode,
|
||||
ComponentExtensionNode node,
|
||||
Dictionary<string, GenericTypeNameRewriter.Binding> bindings)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace().Content;
|
||||
@namespace = string.IsNullOrEmpty(@namespace) ? "__Blazor" : "__Blazor." + @namespace;
|
||||
@namespace += "." + documentNode.FindPrimaryClass().ClassName;
|
||||
|
||||
var typeInferenceNode = new ComponentTypeInferenceMethodIntermediateNode()
|
||||
{
|
||||
Bindings = bindings,
|
||||
Component = node,
|
||||
|
||||
// Method name is generated and guaranteed not to collide, since it's unique for each
|
||||
// component call site.
|
||||
MethodName = $"Create{node.TagName}_{_id++}",
|
||||
FullTypeName = @namespace + ".TypeInference",
|
||||
};
|
||||
|
||||
node.TypeInferenceNode = typeInferenceNode;
|
||||
|
||||
// Now we need to insert the type inference node into the tree.
|
||||
var namespaceNode = documentNode.Children
|
||||
.OfType<NamespaceDeclarationIntermediateNode>()
|
||||
.Where(n => n.Annotations.Contains(new KeyValuePair<object, object>(BlazorMetadata.Component.GenericTypedKey, bool.TrueString)))
|
||||
.FirstOrDefault();
|
||||
if (namespaceNode == null)
|
||||
{
|
||||
namespaceNode = new NamespaceDeclarationIntermediateNode()
|
||||
{
|
||||
Annotations =
|
||||
{
|
||||
{ BlazorMetadata.Component.GenericTypedKey, bool.TrueString },
|
||||
},
|
||||
Content = @namespace,
|
||||
};
|
||||
|
||||
documentNode.Children.Add(namespaceNode);
|
||||
}
|
||||
|
||||
var classNode = namespaceNode.Children
|
||||
.OfType<ClassDeclarationIntermediateNode>()
|
||||
.Where(n => n.ClassName == "TypeInference")
|
||||
.FirstOrDefault();
|
||||
if (classNode == null)
|
||||
{
|
||||
classNode = new ClassDeclarationIntermediateNode()
|
||||
{
|
||||
ClassName = "TypeInference",
|
||||
Modifiers =
|
||||
{
|
||||
"internal",
|
||||
"static",
|
||||
},
|
||||
};
|
||||
namespaceNode.Children.Add(classNode);
|
||||
}
|
||||
|
||||
classNode.Children.Add(typeInferenceNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class GenericTypeNameRewriter : CSharpSyntaxRewriter
|
||||
{
|
||||
private readonly Dictionary<string, Binding> _bindings;
|
||||
|
||||
public GenericTypeNameRewriter(Dictionary<string, Binding> bindings)
|
||||
{
|
||||
_bindings = bindings;
|
||||
}
|
||||
|
||||
public override SyntaxNode Visit(SyntaxNode node)
|
||||
{
|
||||
// We can handle a single IdentifierNameSyntax at the top level (like 'TItem)
|
||||
// OR a GenericNameSyntax recursively (like `List<T>`)
|
||||
if (node is IdentifierNameSyntax identifier && !(identifier.Parent is QualifiedNameSyntax))
|
||||
{
|
||||
if (_bindings.TryGetValue(identifier.Identifier.Text, out var binding))
|
||||
{
|
||||
// If we don't have a valid replacement, use object. This will make the code at least reasonable
|
||||
// compared to leaving the type parameter in place.
|
||||
//
|
||||
// We add our own diagnostics for missing/invalid type parameters anyway.
|
||||
var replacement = binding?.Content == null ? typeof(object).FullName : binding.Content;
|
||||
return identifier.Update(SyntaxFactory.Identifier(replacement));
|
||||
}
|
||||
}
|
||||
|
||||
return base.Visit(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitGenericName(GenericNameSyntax node)
|
||||
{
|
||||
var args = node.TypeArgumentList.Arguments;
|
||||
for (var i = 0; i < args.Count; i++)
|
||||
{
|
||||
var typeArgument = args[i];
|
||||
args = args.Replace(typeArgument, (TypeSyntax)Visit(typeArgument));
|
||||
}
|
||||
|
||||
return node.WithTypeArgumentList(node.TypeArgumentList.WithArguments(args));
|
||||
}
|
||||
|
||||
public class Binding
|
||||
{
|
||||
public BoundAttributeDescriptor Attribute { get; set; }
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public ComponentTypeArgumentExtensionNode Node { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +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.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// Rewrites type names to use the 'global::' prefix for identifiers.
|
||||
//
|
||||
// This is useful when we're generating code in a different namespace than
|
||||
// what the user code lives in. When we synthesize a namespace it's easy to have
|
||||
// clashes.
|
||||
internal class GlobalQualifiedTypeNameRewriter : CSharpSyntaxRewriter
|
||||
{
|
||||
// List of names to ignore.
|
||||
//
|
||||
// NOTE: this is the list of type parameters defined on the component.
|
||||
private readonly HashSet<string> _ignore;
|
||||
|
||||
public GlobalQualifiedTypeNameRewriter(IEnumerable<string> ignore)
|
||||
{
|
||||
_ignore = new HashSet<string>(ignore);
|
||||
}
|
||||
|
||||
public override SyntaxNode Visit(SyntaxNode node)
|
||||
{
|
||||
return base.Visit(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node)
|
||||
{
|
||||
if (node.Parent is QualifiedNameSyntax)
|
||||
{
|
||||
return base.VisitQualifiedName(node);
|
||||
}
|
||||
|
||||
// Need to rewrite postorder so we can rewrite the names of generic type arguments.
|
||||
node = (QualifiedNameSyntax)base.VisitQualifiedName(node);
|
||||
|
||||
// Rewriting these is complicated, best to just tostring and parse again.
|
||||
return SyntaxFactory.ParseTypeName("global::" + node.ToString());
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
|
||||
{
|
||||
if (_ignore.Contains(node.ToString()))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
if (node.Parent != null)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
return SyntaxFactory.AliasQualifiedName(SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword)), node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +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.Diagnostics;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
internal class HtmlBlockIntermediateNode : ExtensionIntermediateNode
|
||||
{
|
||||
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<HtmlBlockIntermediateNode>(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));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteHtmlBlock(context, this);
|
||||
}
|
||||
|
||||
private string DebuggerDisplay => Content;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,349 +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 System.Text;
|
||||
using AngleSharp.Html;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// Rewrites contiguous subtrees of HTML into a special node type to reduce the
|
||||
// size of the Render tree.
|
||||
//
|
||||
// Does not preserve insignificant details of the HTML, like tag closing style
|
||||
// or quote style.
|
||||
internal class HtmlBlockPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
// Runs LATE because we want to destroy structure.
|
||||
public override int Order => 10000;
|
||||
|
||||
protected override void ExecuteCore(
|
||||
RazorCodeDocument codeDocument,
|
||||
DocumentIntermediateNode documentNode)
|
||||
{
|
||||
if (documentNode.Options.DesignTime)
|
||||
{
|
||||
// Nothing to do during design time.
|
||||
return;
|
||||
}
|
||||
|
||||
var findVisitor = new FindHtmlTreeVisitor();
|
||||
findVisitor.Visit(documentNode);
|
||||
|
||||
var trees = findVisitor.Trees;
|
||||
var rewriteVisitor = new RewriteVisitor(trees);
|
||||
while (trees.Count > 0)
|
||||
{
|
||||
// Walk backwards since we did a postorder traversal.
|
||||
var reference = trees[trees.Count - 1];
|
||||
|
||||
// Forcibly remove a node to prevent infinite loops.
|
||||
trees.RemoveAt(trees.Count - 1);
|
||||
|
||||
// We want to fold together siblings where possible. To do this, first we find
|
||||
// the index of the node we're looking at now - then we need to walk backwards
|
||||
// and identify a set of contiguous nodes we can merge.
|
||||
var start = reference.Parent.Children.Count - 1;
|
||||
for (; start >= 0; start--)
|
||||
{
|
||||
if (ReferenceEquals(reference.Node, reference.Parent.Children[start]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the current node. Check if the left sibling is always a candidate
|
||||
// for rewriting. Due to the order we processed the nodes, we know that the
|
||||
// left sibling is next in the list to process if it's a candidate.
|
||||
var end = start;
|
||||
while (start - 1 >= 0)
|
||||
{
|
||||
var candidate = reference.Parent.Children[start - 1];
|
||||
if (trees.Count == 0 || !ReferenceEquals(trees[trees.Count - 1].Node, candidate))
|
||||
{
|
||||
// This means the we're out of nodes, or the left sibling is not in the list.
|
||||
break;
|
||||
}
|
||||
|
||||
// This means that the left sibling is valid to merge.
|
||||
start--;
|
||||
|
||||
// Remove this since we're combining it.
|
||||
trees.RemoveAt(trees.Count - 1);
|
||||
}
|
||||
|
||||
// As a degenerate case, don't bother rewriting an single HtmlContent node
|
||||
// It doesn't add any value.
|
||||
if (end - start == 0 && reference.Node is HtmlContentIntermediateNode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we know the range of nodes to rewrite (end is inclusive)
|
||||
var length = end + 1 - start;
|
||||
while (length > 0)
|
||||
{
|
||||
// Keep using start since we're removing nodes.
|
||||
var node = reference.Parent.Children[start];
|
||||
reference.Parent.Children.RemoveAt(start);
|
||||
|
||||
rewriteVisitor.Visit(node);
|
||||
|
||||
length--;
|
||||
}
|
||||
|
||||
reference.Parent.Children.Insert(start, new HtmlBlockIntermediateNode()
|
||||
{
|
||||
Content = rewriteVisitor.Builder.ToString(),
|
||||
});
|
||||
|
||||
rewriteVisitor.Builder.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Finds HTML-blocks using a postorder traversal. We store nodes in an
|
||||
// ordered list so we can avoid redundant rewrites.
|
||||
//
|
||||
// Consider a case like:
|
||||
// <div>
|
||||
// <a href="...">click me</a>
|
||||
// </div>
|
||||
//
|
||||
// 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<HtmlElementIntermediateNode>
|
||||
{
|
||||
private bool _foundNonHtml;
|
||||
|
||||
public List<IntermediateNodeReference> Trees { get; } = new List<IntermediateNodeReference>();
|
||||
|
||||
public override void VisitDefault(IntermediateNode node)
|
||||
{
|
||||
// If we get here, we found a non-HTML node. Keep traversing.
|
||||
_foundNonHtml = true;
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
|
||||
public void VisitExtension(HtmlElementIntermediateNode node)
|
||||
{
|
||||
// We need to restore the state after processing this node.
|
||||
// We might have found a leaf-block of HTML, but that shouldn't
|
||||
// affect our parent's state.
|
||||
var originalState = _foundNonHtml;
|
||||
|
||||
_foundNonHtml = false;
|
||||
|
||||
if (node.HasDiagnostics)
|
||||
{
|
||||
// Treat node with errors as non-HTML - don't let the parent rewrite this either.
|
||||
_foundNonHtml = true;
|
||||
}
|
||||
|
||||
if (string.Equals("script", node.TagName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Treat script tags as non-HTML - we trigger errors for script tags
|
||||
// later.
|
||||
_foundNonHtml = true;
|
||||
}
|
||||
|
||||
base.VisitDefault(node);
|
||||
|
||||
if (!_foundNonHtml)
|
||||
{
|
||||
Trees.Add(new IntermediateNodeReference(Parent, node));
|
||||
}
|
||||
|
||||
_foundNonHtml = originalState |= _foundNonHtml;
|
||||
}
|
||||
|
||||
public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
|
||||
{
|
||||
if (node.HasDiagnostics)
|
||||
{
|
||||
// Treat node with errors as non-HTML
|
||||
_foundNonHtml = true;
|
||||
}
|
||||
|
||||
// Visit Children
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
|
||||
public override void VisitHtmlAttributeValue(HtmlAttributeValueIntermediateNode node)
|
||||
{
|
||||
if (node.HasDiagnostics)
|
||||
{
|
||||
// Treat node with errors as non-HTML
|
||||
_foundNonHtml = true;
|
||||
}
|
||||
|
||||
// Visit Children
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
|
||||
public override void VisitHtml(HtmlContentIntermediateNode node)
|
||||
{
|
||||
// We need to restore the state after processing this node.
|
||||
// We might have found a leaf-block of HTML, but that shouldn't
|
||||
// affect our parent's state.
|
||||
var originalState = _foundNonHtml;
|
||||
|
||||
_foundNonHtml = false;
|
||||
|
||||
if (node.HasDiagnostics)
|
||||
{
|
||||
// Treat node with errors as non-HTML
|
||||
_foundNonHtml = true;
|
||||
}
|
||||
|
||||
// Visit Children
|
||||
base.VisitDefault(node);
|
||||
|
||||
if (!_foundNonHtml)
|
||||
{
|
||||
Trees.Add(new IntermediateNodeReference(Parent, node));
|
||||
}
|
||||
|
||||
_foundNonHtml = originalState |= _foundNonHtml;
|
||||
}
|
||||
|
||||
public override void VisitToken(IntermediateToken node)
|
||||
{
|
||||
if (node.HasDiagnostics)
|
||||
{
|
||||
// Treat node with errors as non-HTML
|
||||
_foundNonHtml = true;
|
||||
}
|
||||
|
||||
if (node.IsCSharp)
|
||||
{
|
||||
_foundNonHtml = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RewriteVisitor :
|
||||
IntermediateNodeWalker,
|
||||
IExtensionIntermediateNodeVisitor<HtmlElementIntermediateNode>
|
||||
{
|
||||
private readonly StringBuilder _encodingBuilder;
|
||||
|
||||
private readonly List<IntermediateNodeReference> _trees;
|
||||
|
||||
public RewriteVisitor(List<IntermediateNodeReference> trees)
|
||||
{
|
||||
_trees = trees;
|
||||
|
||||
_encodingBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
public StringBuilder Builder { get; } = new StringBuilder();
|
||||
|
||||
public void VisitExtension(HtmlElementIntermediateNode node)
|
||||
{
|
||||
for (var i = 0; i < _trees.Count; i++)
|
||||
{
|
||||
// Remove this node if it's in the list. This ensures that we don't
|
||||
// do redundant operations.
|
||||
if (ReferenceEquals(_trees[i].Node, node))
|
||||
{
|
||||
_trees.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var isVoid = ComponentDocumentRewritePass.VoidElements.Contains(node.TagName);
|
||||
var hasBodyContent = node.Body.Any();
|
||||
|
||||
Builder.Append("<");
|
||||
Builder.Append(node.TagName);
|
||||
|
||||
foreach (var attribute in node.Attributes)
|
||||
{
|
||||
Visit(attribute);
|
||||
}
|
||||
|
||||
// If for some reason a void element contains body, then treat it as a
|
||||
// start/end tag.
|
||||
if (!hasBodyContent && isVoid)
|
||||
{
|
||||
// void
|
||||
Builder.Append(">");
|
||||
return;
|
||||
}
|
||||
else if (!hasBodyContent)
|
||||
{
|
||||
// In HTML5, we can't have self-closing non-void elements, so explicitly
|
||||
// add a close tag
|
||||
Builder.Append("></");
|
||||
Builder.Append(node.TagName);
|
||||
Builder.Append(">");
|
||||
return;
|
||||
}
|
||||
|
||||
// start/end tag with body.
|
||||
Builder.Append(">");
|
||||
|
||||
foreach (var item in node.Body)
|
||||
{
|
||||
Visit(item);
|
||||
}
|
||||
|
||||
Builder.Append("</");
|
||||
Builder.Append(node.TagName);
|
||||
Builder.Append(">");
|
||||
}
|
||||
|
||||
public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
|
||||
{
|
||||
Builder.Append(" ");
|
||||
Builder.Append(node.AttributeName);
|
||||
|
||||
if (node.Children.Count == 0)
|
||||
{
|
||||
// Minimized attribute
|
||||
return;
|
||||
}
|
||||
|
||||
Builder.Append("=\"");
|
||||
|
||||
// Visit Children
|
||||
base.VisitDefault(node);
|
||||
|
||||
Builder.Append("\"");
|
||||
}
|
||||
|
||||
public override void VisitHtmlAttributeValue(HtmlAttributeValueIntermediateNode node)
|
||||
{
|
||||
Builder.Append(Encode(node.Children));
|
||||
}
|
||||
|
||||
public override void VisitHtml(HtmlContentIntermediateNode node)
|
||||
{
|
||||
Builder.Append(Encode(node.Children));
|
||||
}
|
||||
|
||||
private string Encode(IntermediateNodeCollection nodes)
|
||||
{
|
||||
// We need to HTML encode text content. We would have decoded HTML entities
|
||||
// earlier when we parsed the text into a tree, but since we're folding
|
||||
// this node into a block of pre-encoded HTML we need to be sure to
|
||||
// re-encode.
|
||||
_encodingBuilder.Clear();
|
||||
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
_encodingBuilder.Append(((IntermediateToken)nodes[i]).Content);
|
||||
}
|
||||
|
||||
return HtmlMarkupFormatter.Instance.Text(_encodingBuilder.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
internal class HtmlElementIntermediateNode : ExtensionIntermediateNode
|
||||
{
|
||||
public IEnumerable<HtmlAttributeIntermediateNode> Attributes => Children.OfType<HtmlAttributeIntermediateNode>();
|
||||
|
||||
public IEnumerable<RefExtensionNode> Captures => Children.OfType<RefExtensionNode>();
|
||||
|
||||
public IEnumerable<IntermediateNode> Body => Children.Where(c =>
|
||||
{
|
||||
return
|
||||
c as HtmlAttributeIntermediateNode == null &&
|
||||
c as RefExtensionNode == null;
|
||||
});
|
||||
|
||||
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||
|
||||
public string TagName { get; set; }
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<HtmlElementIntermediateNode>(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));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal static class ImplementsDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
|
||||
"implements",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.AddTypeToken(Resources.ImplementsDirective_TypeToken_Name, Resources.ImplementsDirective_TypeToken_Description);
|
||||
builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
|
||||
builder.Description = Resources.ImplementsDirective_Description;
|
||||
});
|
||||
|
||||
public static void Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new ImplementsDirectivePass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +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.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.Components.Razor
|
||||
{
|
||||
internal class ImplementsDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@class == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (@class.Interfaces == null)
|
||||
{
|
||||
@class.Interfaces = new List<string>();
|
||||
}
|
||||
|
||||
foreach (var implements in documentNode.FindDirectiveReferences(ImplementsDirective.Directive))
|
||||
{
|
||||
var token = ((DirectiveIntermediateNode)implements.Node).Tokens.FirstOrDefault();
|
||||
if (token != null)
|
||||
{
|
||||
@class.Interfaces.Add(token.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal static class LayoutDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
|
||||
"layout",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.AddTypeToken(Resources.LayoutDirective_TypeToken_Name, Resources.LayoutDirective_TypeToken_Description);
|
||||
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring;
|
||||
builder.Description = Resources.LayoutDirective_Description;
|
||||
});
|
||||
|
||||
public static void Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new LayoutDirectivePass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class LayoutDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var directives = documentNode.FindDirectiveReferences(LayoutDirective.Directive);
|
||||
if (directives.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var token = ((DirectiveIntermediateNode)directives[0].Node).Tokens.FirstOrDefault();
|
||||
if (token == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var attributeNode = new CSharpCodeIntermediateNode();
|
||||
attributeNode.Children.Add(new IntermediateToken()
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $"[{ComponentsApi.LayoutAttribute.FullTypeName}(typeof({token.Content}))]" + Environment.NewLine,
|
||||
});
|
||||
|
||||
// Insert the new attribute on top of the class
|
||||
for (var i = 0; i < @namespace.Children.Count; i++)
|
||||
{
|
||||
if (object.ReferenceEquals(@namespace.Children[i], @class))
|
||||
{
|
||||
@namespace.Children.Insert(i, attributeNode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">$(TargetFrameworks);net461</TargetFrameworks>
|
||||
<RootNamespace>Microsoft.AspNetCore.Components.Razor</RootNamespace>
|
||||
<Description>Extensions to the Razor compiler to support building Razor Components.</Description>
|
||||
<IsProductPackage>true</IsProductPackage>
|
||||
|
||||
<!-- Copy package references to output, needed so the build project can find them -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="0.9.9.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="$(RazorPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Razor" Version="$(RazorPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\shared\ComponentsApi.cs" Link="shared\%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,44 +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.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class PageDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
|
||||
"page",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.AddStringToken(Resources.PageDirective_RouteToken_Name, Resources.PageDirective_RouteToken_Description);
|
||||
builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
|
||||
builder.Description = Resources.PageDirective_Description;
|
||||
});
|
||||
|
||||
private PageDirective(string routeTemplate, IntermediateNode directiveNode)
|
||||
{
|
||||
RouteTemplate = routeTemplate;
|
||||
DirectiveNode = directiveNode;
|
||||
}
|
||||
|
||||
public string RouteTemplate { get; }
|
||||
|
||||
public IntermediateNode DirectiveNode { get; }
|
||||
|
||||
public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new PageDirectivePass());
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +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.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class PageDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
if (codeDocument == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
if (documentNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentNode));
|
||||
}
|
||||
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var directives = documentNode.FindDirectiveReferences(PageDirective.Directive);
|
||||
if (directives.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't allow @page directives in imports
|
||||
for (var i = 0; i < directives.Count; i++)
|
||||
{
|
||||
var directive = directives[i];
|
||||
if (directive.Node.IsImported())
|
||||
{
|
||||
directive.Node.Diagnostics.Add(BlazorDiagnosticFactory.CreatePageDirective_CannotBeImported(directive.Node.Source.Value));
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the attributes 'on-top' of the class declaration, since classes don't directly support attributes.
|
||||
var index = 0;
|
||||
for (; index < @namespace.Children.Count; index++)
|
||||
{
|
||||
if (object.ReferenceEquals(@class, @namespace.Children[index]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < directives.Count; i++)
|
||||
{
|
||||
var pageDirective = (DirectiveIntermediateNode)directives[i].Node;
|
||||
|
||||
// The parser also adds errors for invalid syntax, we just need to not crash.
|
||||
var routeToken = pageDirective.Tokens.FirstOrDefault();
|
||||
|
||||
if (routeToken != null &&
|
||||
routeToken.Content.Length >= 3 &&
|
||||
routeToken.Content[0] == '\"' &&
|
||||
routeToken.Content[1] == '/' &&
|
||||
routeToken.Content[routeToken.Content.Length - 1] == '\"')
|
||||
{
|
||||
var template = routeToken.Content.Substring(1, routeToken.Content.Length - 2);
|
||||
@namespace.Children.Insert(index++, new RouteAttributeExtensionNode(template));
|
||||
}
|
||||
else
|
||||
{
|
||||
pageDirective.Diagnostics.Add(BlazorDiagnosticFactory.CreatePageDirective_MustSpecifyRoute(pageDirective.Source));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +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.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Components.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
// These should match the configuration names specified by the Blazor MSBuild files.
|
||||
[assembly: ProvideRazorExtensionInitializer("Blazor-0.1", typeof(BlazorExtensionInitializer))]
|
||||
[assembly: ProvideRazorExtensionInitializer("BlazorDeclaration-0.1", typeof(BlazorExtensionInitializer))]
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Build.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -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.Components.Razor
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class RefExtensionNode : ExtensionIntermediateNode
|
||||
{
|
||||
public RefExtensionNode(IntermediateToken identifierToken)
|
||||
{
|
||||
IdentifierToken = identifierToken ?? throw new ArgumentNullException(nameof(identifierToken));
|
||||
Source = IdentifierToken.Source;
|
||||
}
|
||||
|
||||
public RefExtensionNode(IntermediateToken identifierToken, string componentCaptureTypeName)
|
||||
: this(identifierToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(componentCaptureTypeName))
|
||||
{
|
||||
throw new ArgumentException("Cannot be null or empty", nameof(componentCaptureTypeName));
|
||||
}
|
||||
|
||||
IsComponentCapture = true;
|
||||
ComponentCaptureTypeName = componentCaptureTypeName;
|
||||
}
|
||||
|
||||
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
|
||||
|
||||
public IntermediateToken IdentifierToken { get; }
|
||||
|
||||
public bool IsComponentCapture { get; }
|
||||
|
||||
public string ComponentCaptureTypeName { get; set; }
|
||||
|
||||
public string TypeName => $"global::System.Action<{(IsComponentCapture ? ComponentCaptureTypeName : "global::" + ComponentsApi.ElementRef.FullTypeName)}>";
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor)
|
||||
{
|
||||
if (visitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(visitor));
|
||||
}
|
||||
|
||||
AcceptExtensionNode<RefExtensionNode>(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));
|
||||
}
|
||||
|
||||
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||
writer.WriteReferenceCapture(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +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.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class RefLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
// Run after component lowering pass
|
||||
public override int Order => 50;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
// Nothing to do, bail. We can't function without the standard structure.
|
||||
return;
|
||||
}
|
||||
|
||||
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
|
||||
for (var i = 0; i < references.Count; i++)
|
||||
{
|
||||
var reference = references[i];
|
||||
var node = (TagHelperPropertyIntermediateNode)reference.Node;
|
||||
|
||||
if (node.TagHelper.IsRefTagHelper())
|
||||
{
|
||||
reference.Replace(RewriteUsage(@class, reference.Parent, node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntermediateNode RewriteUsage(ClassDeclarationIntermediateNode classNode, IntermediateNode parent, TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
// If we can't get a nonempty attribute name, do nothing because there will
|
||||
// already be a diagnostic for empty values
|
||||
var identifierToken = DetermineIdentifierToken(node);
|
||||
if (identifierToken == null)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
// Determine whether this is an element capture or a component capture, and
|
||||
// if applicable the type name that will appear in the resulting capture code
|
||||
var componentTagHelper = (parent as ComponentExtensionNode)?.Component;
|
||||
if (componentTagHelper != null)
|
||||
{
|
||||
return new RefExtensionNode(identifierToken, componentTagHelper.GetTypeName());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RefExtensionNode(identifierToken);
|
||||
}
|
||||
}
|
||||
|
||||
private IntermediateToken DetermineIdentifierToken(TagHelperPropertyIntermediateNode attributeNode)
|
||||
{
|
||||
IntermediateToken foundToken = null;
|
||||
|
||||
if (attributeNode.Children.Count == 1)
|
||||
{
|
||||
if (attributeNode.Children[0] is IntermediateToken token)
|
||||
{
|
||||
foundToken = token;
|
||||
}
|
||||
else if (attributeNode.Children[0] is CSharpExpressionIntermediateNode csharpNode)
|
||||
{
|
||||
if (csharpNode.Children.Count == 1)
|
||||
{
|
||||
foundToken = csharpNode.Children[0] as IntermediateToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !string.IsNullOrWhiteSpace(foundToken?.Content) ? foundToken : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class RefTagHelperDescriptorProvider : ITagHelperDescriptorProvider
|
||||
{
|
||||
// Run after the component tag helper provider, because later we may want component-type-specific variants of this
|
||||
public int Order { get; set; } = 1000;
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
context.Results.Add(CreateRefTagHelper());
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateRefTagHelper()
|
||||
{
|
||||
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Ref.TagHelperKind, "Ref", ComponentsApi.AssemblyName);
|
||||
builder.Documentation = Resources.RefTagHelper_Documentation;
|
||||
|
||||
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Ref.TagHelperKind);
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Ref.RuntimeName;
|
||||
|
||||
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the tooltips.
|
||||
builder.SetTypeName("Microsoft.AspNetCore.Components.Ref");
|
||||
|
||||
builder.TagMatchingRule(rule =>
|
||||
{
|
||||
rule.TagName = "*";
|
||||
rule.Attribute(attribute =>
|
||||
{
|
||||
attribute.Name = "ref";
|
||||
});
|
||||
});
|
||||
|
||||
builder.BindAttribute(attribute =>
|
||||
{
|
||||
attribute.Documentation = Resources.RefTagHelper_Documentation;
|
||||
attribute.Name = "ref";
|
||||
|
||||
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||
// a C# property will crash trying to create the tooltips.
|
||||
attribute.SetPropertyName("Ref");
|
||||
attribute.TypeName = typeof(object).FullName;
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNetCore.Components.Razor.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Binds the provided expression to the '{0}' property and a change event delegate to the '{1}' property of the component..
|
||||
/// </summary>
|
||||
internal static string BindTagHelper_Component_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("BindTagHelper_Component_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Binds the provided expression to the '{0}' attribute and a change event delegate to the '{1}' attribute..
|
||||
/// </summary>
|
||||
internal static string BindTagHelper_Element_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("BindTagHelper_Element_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies a format to convert the value specified by the '{0}' attribute. The format string can currently only be used with expressions of type <code>DateTime</code>..
|
||||
/// </summary>
|
||||
internal static string BindTagHelper_Element_Format_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("BindTagHelper_Element_Format_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Binds the provided expression to an attribute and a change event, based on the naming of the bind attribute. For example: <code>bind-value-onchange="..."</code> will assign the current value of the expression to the 'value' attribute, and assign a delegate that attempts to set the value to the 'onchange' attribute..
|
||||
/// </summary>
|
||||
internal static string BindTagHelper_Fallback_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("BindTagHelper_Fallback_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies a format to convert the value specified by the corresponding bind attribute. For example: <code>format-value="..."</code> will apply a format string to the value specified in <code>bind-value-...</code>. The format string can currently only be used with expressions of type <code>DateTime</code>..
|
||||
/// </summary>
|
||||
internal static string BindTagHelper_Fallback_Format_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("BindTagHelper_Fallback_Format_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the parameter name for the '{0}' child content expression..
|
||||
/// </summary>
|
||||
internal static string ChildContentParameterName_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("ChildContentParameterName_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the parameter name for all child content expressions..
|
||||
/// </summary>
|
||||
internal static string ChildContentParameterName_TopLevelDocumentation {
|
||||
get {
|
||||
return ResourceManager.GetString("ChildContentParameterName_TopLevelDocumentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the type of the type parameter {0} for the {1} component..
|
||||
/// </summary>
|
||||
internal static string ComponentTypeParameter_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("ComponentTypeParameter_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Sets the '{0}' attribute to the provided string or delegate value. A delegate value should be of type '{1}'..
|
||||
/// </summary>
|
||||
internal static string EventHandlerTagHelper_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("EventHandlerTagHelper_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Declares an interface implementation for the current document..
|
||||
/// </summary>
|
||||
internal static string ImplementsDirective_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("ImplementsDirective_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The interface type implemented by the current document..
|
||||
/// </summary>
|
||||
internal static string ImplementsDirective_TypeToken_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("ImplementsDirective_TypeToken_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to TypeName.
|
||||
/// </summary>
|
||||
internal static string ImplementsDirective_TypeToken_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("ImplementsDirective_TypeToken_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Declares a layout type for the current document..
|
||||
/// </summary>
|
||||
internal static string LayoutDirective_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("LayoutDirective_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The interface type implemented by the current document..
|
||||
/// </summary>
|
||||
internal static string LayoutDirective_TypeToken_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("LayoutDirective_TypeToken_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to TypeName.
|
||||
/// </summary>
|
||||
internal static string LayoutDirective_TypeToken_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("LayoutDirective_TypeToken_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Mark the page as a routable component..
|
||||
/// </summary>
|
||||
internal static string PageDirective_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("PageDirective_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An optional route template for the component..
|
||||
/// </summary>
|
||||
internal static string PageDirective_RouteToken_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("PageDirective_RouteToken_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to route template.
|
||||
/// </summary>
|
||||
internal static string PageDirective_RouteToken_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("PageDirective_RouteToken_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file.
|
||||
/// </summary>
|
||||
internal static string PageDirectiveCannotBeImported {
|
||||
get {
|
||||
return ResourceManager.GetString("PageDirectiveCannotBeImported", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Populates the specified field or property with a reference to the element or component..
|
||||
/// </summary>
|
||||
internal static string RefTagHelper_Documentation {
|
||||
get {
|
||||
return ResourceManager.GetString("RefTagHelper_Documentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Declares a generic type parameter for the generated component class..
|
||||
/// </summary>
|
||||
internal static string TypeParamDirective_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("TypeParamDirective_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The name of the type parameter..
|
||||
/// </summary>
|
||||
internal static string TypeParamDirective_Token_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("TypeParamDirective_Token_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to type parameter.
|
||||
/// </summary>
|
||||
internal static string TypeParamDirective_Token_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("TypeParamDirective_Token_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="BindTagHelper_Component_Documentation" xml:space="preserve">
|
||||
<value>Binds the provided expression to the '{0}' property and a change event delegate to the '{1}' property of the component.</value>
|
||||
</data>
|
||||
<data name="BindTagHelper_Element_Documentation" xml:space="preserve">
|
||||
<value>Binds the provided expression to the '{0}' attribute and a change event delegate to the '{1}' attribute.</value>
|
||||
</data>
|
||||
<data name="BindTagHelper_Element_Format_Documentation" xml:space="preserve">
|
||||
<value>Specifies a format to convert the value specified by the '{0}' attribute. The format string can currently only be used with expressions of type <code>DateTime</code>.</value>
|
||||
</data>
|
||||
<data name="BindTagHelper_Fallback_Documentation" xml:space="preserve">
|
||||
<value>Binds the provided expression to an attribute and a change event, based on the naming of the bind attribute. For example: <code>bind-value-onchange="..."</code> will assign the current value of the expression to the 'value' attribute, and assign a delegate that attempts to set the value to the 'onchange' attribute.</value>
|
||||
</data>
|
||||
<data name="BindTagHelper_Fallback_Format_Documentation" xml:space="preserve">
|
||||
<value>Specifies a format to convert the value specified by the corresponding bind attribute. For example: <code>format-value="..."</code> will apply a format string to the value specified in <code>bind-value-...</code>. The format string can currently only be used with expressions of type <code>DateTime</code>.</value>
|
||||
</data>
|
||||
<data name="ChildContentParameterName_Documentation" xml:space="preserve">
|
||||
<value>Specifies the parameter name for the '{0}' child content expression.</value>
|
||||
</data>
|
||||
<data name="ChildContentParameterName_TopLevelDocumentation" xml:space="preserve">
|
||||
<value>Specifies the parameter name for all child content expressions.</value>
|
||||
</data>
|
||||
<data name="ComponentTypeParameter_Documentation" xml:space="preserve">
|
||||
<value>Specifies the type of the type parameter {0} for the {1} component.</value>
|
||||
</data>
|
||||
<data name="EventHandlerTagHelper_Documentation" xml:space="preserve">
|
||||
<value>Sets the '{0}' attribute to the provided string or delegate value. A delegate value should be of type '{1}'.</value>
|
||||
</data>
|
||||
<data name="ImplementsDirective_Description" xml:space="preserve">
|
||||
<value>Declares an interface implementation for the current document.</value>
|
||||
</data>
|
||||
<data name="ImplementsDirective_TypeToken_Description" xml:space="preserve">
|
||||
<value>The interface type implemented by the current document.</value>
|
||||
</data>
|
||||
<data name="ImplementsDirective_TypeToken_Name" xml:space="preserve">
|
||||
<value>TypeName</value>
|
||||
</data>
|
||||
<data name="LayoutDirective_Description" xml:space="preserve">
|
||||
<value>Declares a layout type for the current document.</value>
|
||||
</data>
|
||||
<data name="LayoutDirective_TypeToken_Description" xml:space="preserve">
|
||||
<value>The interface type implemented by the current document.</value>
|
||||
</data>
|
||||
<data name="LayoutDirective_TypeToken_Name" xml:space="preserve">
|
||||
<value>TypeName</value>
|
||||
</data>
|
||||
<data name="PageDirectiveCannotBeImported" xml:space="preserve">
|
||||
<value>The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file</value>
|
||||
</data>
|
||||
<data name="PageDirective_Description" xml:space="preserve">
|
||||
<value>Mark the page as a routable component.</value>
|
||||
</data>
|
||||
<data name="PageDirective_RouteToken_Description" xml:space="preserve">
|
||||
<value>An optional route template for the component.</value>
|
||||
</data>
|
||||
<data name="PageDirective_RouteToken_Name" xml:space="preserve">
|
||||
<value>route template</value>
|
||||
</data>
|
||||
<data name="RefTagHelper_Documentation" xml:space="preserve">
|
||||
<value>Populates the specified field or property with a reference to the element or component.</value>
|
||||
</data>
|
||||
<data name="TypeParamDirective_Description" xml:space="preserve">
|
||||
<value>Declares a generic type parameter for the generated component class.</value>
|
||||
</data>
|
||||
<data name="TypeParamDirective_Token_Description" xml:space="preserve">
|
||||
<value>The name of the type parameter.</value>
|
||||
</data>
|
||||
<data name="TypeParamDirective_Token_Name" xml:space="preserve">
|
||||
<value>type parameter</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -1,34 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class RouteAttributeExtensionNode : ExtensionIntermediateNode
|
||||
{
|
||||
public RouteAttributeExtensionNode(string template)
|
||||
{
|
||||
Template = template;
|
||||
}
|
||||
|
||||
public string Template { get; }
|
||||
|
||||
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
|
||||
|
||||
public override void Accept(IntermediateNodeVisitor visitor) => AcceptExtensionNode(this, visitor);
|
||||
|
||||
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
|
||||
{
|
||||
context.CodeWriter.Write("[");
|
||||
context.CodeWriter.Write(ComponentsApi.RouteAttribute.FullTypeName);
|
||||
context.CodeWriter.Write("(\"");
|
||||
context.CodeWriter.Write(Template);
|
||||
context.CodeWriter.Write("\")");
|
||||
context.CodeWriter.Write("]");
|
||||
context.CodeWriter.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +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 Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Keeps track of the nesting of elements/containers while writing out the C# source code
|
||||
/// for a component. This allows us to detect mismatched start/end tags, as well as inject
|
||||
/// additional C# source to capture component descendants in a lambda.
|
||||
/// </summary>
|
||||
internal class ScopeStack
|
||||
{
|
||||
private readonly Stack<ScopeEntry> _stack = new Stack<ScopeEntry>();
|
||||
private int _builderVarNumber = 1;
|
||||
|
||||
public string BuilderVarName { get; private set; } = "builder";
|
||||
|
||||
public void OpenComponentScope(CodeRenderingContext context, string name, string parameterName)
|
||||
{
|
||||
var scope = new ScopeEntry(name, ScopeKind.Component);
|
||||
_stack.Push(scope);
|
||||
|
||||
OffsetBuilderVarNumber(1);
|
||||
|
||||
// Writes code that looks like:
|
||||
//
|
||||
// ((__builder) => { ... })
|
||||
// OR
|
||||
// ((context) => (__builder) => { ... })
|
||||
|
||||
if (parameterName != null)
|
||||
{
|
||||
context.CodeWriter.Write($"({parameterName}) => ");
|
||||
}
|
||||
|
||||
scope.LambdaScope = context.CodeWriter.BuildLambda(BuilderVarName);
|
||||
}
|
||||
|
||||
public void OpenTemplateScope(CodeRenderingContext context)
|
||||
{
|
||||
var currentScope = new ScopeEntry("__template", ScopeKind.Template);
|
||||
_stack.Push(currentScope);
|
||||
|
||||
// Templates always get a lambda scope, because they are defined as a lambda.
|
||||
OffsetBuilderVarNumber(1);
|
||||
currentScope.LambdaScope = context.CodeWriter.BuildLambda(BuilderVarName);
|
||||
}
|
||||
|
||||
public void CloseScope(CodeRenderingContext context)
|
||||
{
|
||||
var currentScope = _stack.Pop();
|
||||
currentScope.LambdaScope.Dispose();
|
||||
OffsetBuilderVarNumber(-1);
|
||||
}
|
||||
|
||||
private void OffsetBuilderVarNumber(int delta)
|
||||
{
|
||||
_builderVarNumber += delta;
|
||||
BuilderVarName = _builderVarNumber == 1
|
||||
? "builder"
|
||||
: $"builder{_builderVarNumber}";
|
||||
}
|
||||
|
||||
private class ScopeEntry
|
||||
{
|
||||
public readonly string Name;
|
||||
public ScopeKind Kind;
|
||||
public int ChildCount;
|
||||
public IDisposable LambdaScope;
|
||||
|
||||
public ScopeEntry(string name, ScopeKind kind)
|
||||
{
|
||||
Name = name;
|
||||
Kind = kind;
|
||||
ChildCount = 0;
|
||||
}
|
||||
|
||||
public override string ToString() => $"<{Name}> ({Kind})";
|
||||
}
|
||||
|
||||
private enum ScopeKind
|
||||
{
|
||||
Component,
|
||||
Template,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Html;
|
||||
using AngleSharp.Parser.Html;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class ScriptTagPass : IntermediateNodePassBase, IRazorDocumentClassifierPass
|
||||
{
|
||||
// Run as soon as possible after the Component rewrite pass
|
||||
public override int Order => ComponentDocumentClassifierPass.DefaultFeatureOrder + 2;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
if (documentNode.DocumentKind != ComponentDocumentClassifierPass.ComponentDocumentKind)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(documentNode);
|
||||
}
|
||||
|
||||
private class Visitor : IntermediateNodeWalker, IExtensionIntermediateNodeVisitor<HtmlElementIntermediateNode>
|
||||
{
|
||||
public void VisitExtension(HtmlElementIntermediateNode node)
|
||||
{
|
||||
// Disallow <script> in components as per #552
|
||||
if (string.Equals(node.TagName, "script", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
// We allow you to suppress this error like:
|
||||
// <script suppress-error="BL9992" />
|
||||
var attribute = node.Children[i] as HtmlAttributeIntermediateNode;
|
||||
if (attribute != null &&
|
||||
attribute.AttributeName == "suppress-error" &&
|
||||
attribute.Children.Count == 1 &&
|
||||
attribute.Children[0] is HtmlAttributeValueIntermediateNode value &&
|
||||
value.Children.Count == 1 &&
|
||||
value.Children[0] is IntermediateToken token &&
|
||||
token.IsHtml &&
|
||||
string.Equals(token.Content, "BL9992", StringComparison.Ordinal))
|
||||
{
|
||||
node.Children.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var diagnostic = BlazorDiagnosticFactory.Create_DisallowedScriptTag(node.Source);
|
||||
node.Diagnostics.Add(diagnostic);
|
||||
}
|
||||
|
||||
base.VisitDefault(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,156 +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.Components.Shared;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal static class TagHelperBoundAttributeDescriptorExtensions
|
||||
{
|
||||
public static bool IsDelegateProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.DelegateSignatureKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
public static bool IsGenericTypedProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return
|
||||
attribute.Metadata.TryGetValue(BlazorMetadata.Component.GenericTypedKey, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
public static bool IsTypeParameterProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return
|
||||
attribute.Metadata.TryGetValue(BlazorMetadata.Component.TypeParameterKey, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
public static bool IsWeaklyTyped(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.WeaklyTypedKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is a child content property. Properties are
|
||||
/// considered child content if they have the type <c>RenderFragment</c> or <c>RenderFragment{T}</c>.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||
/// <returns>Returns <c>true</c> if the property is child content, otherwise <c>false</c>.</returns>
|
||||
public static bool IsChildContentProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.ChildContentKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is a child content property. Properties are
|
||||
/// considered child content if they have the type <c>RenderFragment</c> or <c>RenderFragment{T}</c>.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptorBuilder"/>.</param>
|
||||
/// <returns>Returns <c>true</c> if the property is child content, otherwise <c>false</c>.</returns>
|
||||
public static bool IsChildContentProperty(this BoundAttributeDescriptorBuilder attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.ChildContentKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is a parameterized child content property. Properties are
|
||||
/// considered parameterized child content if they have the type <c>RenderFragment{T}</c> (for some T).
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||
/// <returns>Returns <c>true</c> if the property is parameterized child content, otherwise <c>false</c>.</returns>
|
||||
public static bool IsParameterizedChildContentProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return attribute.IsChildContentProperty() &&
|
||||
!string.Equals(attribute.TypeName, ComponentsApi.RenderFragment.FullTypeName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is a parameterized child content property. Properties are
|
||||
/// considered parameterized child content if they have the type <c>RenderFragment{T}</c> (for some T).
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||
/// <returns>Returns <c>true</c> if the property is parameterized child content, otherwise <c>false</c>.</returns>
|
||||
public static bool IsParameterizedChildContentProperty(this BoundAttributeDescriptorBuilder attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return attribute.IsChildContentProperty() &&
|
||||
!string.Equals(attribute.TypeName, ComponentsApi.RenderFragment.FullTypeName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is used to specify the name of the parameter
|
||||
/// for a parameterized child content property.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||
/// <returns>
|
||||
/// Returns <c>true</c> if the property specifies the name of a parameter for a parameterized child content,
|
||||
/// otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsChildContentParameterNameProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.ChildContentParameterNameKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,199 +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 Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal static class TagHelperDescriptorExtensions
|
||||
{
|
||||
public static bool IsBindTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.SpecialKindKey, out var kind) &&
|
||||
string.Equals(BlazorMetadata.Bind.TagHelperKind, kind);
|
||||
}
|
||||
|
||||
public static bool IsFallbackBindTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.IsBindTagHelper() &&
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.Bind.FallbackKey, out var fallback) &&
|
||||
string.Equals(bool.TrueString, fallback);
|
||||
}
|
||||
|
||||
public static bool IsGenericTypedComponent(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
IsComponentTagHelper(tagHelper) &&
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.Component.GenericTypedKey, out var value) &&
|
||||
string.Equals(bool.TrueString, value);
|
||||
}
|
||||
|
||||
public static bool IsInputElementBindTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.IsBindTagHelper() &&
|
||||
tagHelper.TagMatchingRules.Count == 1 &&
|
||||
string.Equals("input", tagHelper.TagMatchingRules[0].TagName);
|
||||
}
|
||||
|
||||
public static bool IsInputElementFallbackBindTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.IsInputElementBindTagHelper() &&
|
||||
!tagHelper.Metadata.ContainsKey(BlazorMetadata.Bind.TypeAttribute);
|
||||
}
|
||||
|
||||
public static string GetValueAttributeName(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.Bind.ValueAttribute, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetChangeAttributeName(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.Bind.ChangeAttribute, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsChildContentTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.SpecialKindKey, out var value) &&
|
||||
string.Equals(value, BlazorMetadata.ChildContent.TagHelperKind, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static bool IsComponentTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return !tagHelper.Metadata.ContainsKey(BlazorMetadata.SpecialKindKey);
|
||||
}
|
||||
|
||||
public static bool IsEventHandlerTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.SpecialKindKey, out var kind) &&
|
||||
string.Equals(BlazorMetadata.EventHandler.TagHelperKind, kind);
|
||||
}
|
||||
|
||||
public static bool IsRefTagHelper(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
return
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.SpecialKindKey, out var kind) &&
|
||||
string.Equals(BlazorMetadata.Ref.TagHelperKind, kind);
|
||||
}
|
||||
|
||||
public static string GetEventArgsType(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
tagHelper.Metadata.TryGetValue(BlazorMetadata.EventHandler.EventArgsType, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of component attributes that can accept child content (<c>RenderFragment</c> or <c>RenderFragment{T}</c>).
|
||||
/// </summary>
|
||||
/// <param name="tagHelper">The <see cref="TagHelperDescriptor"/>.</param>
|
||||
/// <returns>The child content attributes</returns>
|
||||
public static IEnumerable<BoundAttributeDescriptor> GetChildContentProperties(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
|
||||
{
|
||||
var attribute = tagHelper.BoundAttributes[i];
|
||||
if (attribute.IsChildContentProperty())
|
||||
{
|
||||
yield return attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of component attributes that represent generic type parameters of the component type.
|
||||
/// </summary>
|
||||
/// <param name="tagHelper">The <see cref="TagHelperDescriptor"/>.</param>
|
||||
/// <returns>The type parameter attributes</returns>
|
||||
public static IEnumerable<BoundAttributeDescriptor> GetTypeParameters(this TagHelperDescriptor tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
|
||||
{
|
||||
var attribute = tagHelper.BoundAttributes[i];
|
||||
if (attribute.IsTypeParameterProperty())
|
||||
{
|
||||
yield return attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +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.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class TemplateDiagnosticPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||
{
|
||||
// Runs after components/eventhandlers/ref/bind. We need to check for templates in all of those
|
||||
// places.
|
||||
public override int Order => 150;
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.Visit(documentNode);
|
||||
|
||||
for (var i = 0; i < visitor.Candidates.Count; i++)
|
||||
{
|
||||
var candidate = visitor.Candidates[i];
|
||||
candidate.Parent.Diagnostics.Add(BlazorDiagnosticFactory.Create_TemplateInvalidLocation(candidate.Node.Source));
|
||||
|
||||
// Remove the offending node since we don't know how to render it. This means that the user won't get C#
|
||||
// completion at this location, which is fine because it's inside an HTML attribute.
|
||||
candidate.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
private class Visitor : IntermediateNodeWalker, IExtensionIntermediateNodeVisitor<TemplateIntermediateNode>
|
||||
{
|
||||
public List<IntermediateNodeReference> Candidates { get; } = new List<IntermediateNodeReference>();
|
||||
|
||||
public void VisitExtension(TemplateIntermediateNode node)
|
||||
{
|
||||
// We found a template, let's check where it's located.
|
||||
for (var i = 0; i < Ancestors.Count; i++)
|
||||
{
|
||||
var ancestor = Ancestors[i];
|
||||
|
||||
if (
|
||||
// Inside markup attribute
|
||||
ancestor is HtmlAttributeIntermediateNode ||
|
||||
|
||||
// Inside component attribute
|
||||
ancestor is ComponentAttributeExtensionNode ||
|
||||
|
||||
// Inside malformed ref attribute
|
||||
ancestor is TagHelperPropertyIntermediateNode)
|
||||
{
|
||||
Candidates.Add(new IntermediateNodeReference(Parent, node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +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.Intermediate;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class TrimWhitespacePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
if (codeDocument == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
if (documentNode == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentNode));
|
||||
}
|
||||
|
||||
// There's no benefit running the whitespace trimmer during design-time builds
|
||||
if (!documentNode.Options.DesignTime)
|
||||
{
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
if (method != null)
|
||||
{
|
||||
RemoveContiguousWhitespace(method.Children, TraversalDirection.Forwards);
|
||||
RemoveContiguousWhitespace(method.Children, TraversalDirection.Backwards);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveContiguousWhitespace(IntermediateNodeCollection nodes, TraversalDirection direction)
|
||||
{
|
||||
var position = direction == TraversalDirection.Forwards ? 0 : nodes.Count - 1;
|
||||
while (position >= 0 && position < nodes.Count)
|
||||
{
|
||||
var node = nodes[position];
|
||||
bool shouldRemoveNode;
|
||||
bool shouldContinueIteration;
|
||||
|
||||
switch (node)
|
||||
{
|
||||
case IntermediateToken intermediateToken:
|
||||
shouldRemoveNode = string.IsNullOrWhiteSpace(intermediateToken.Content);
|
||||
shouldContinueIteration = shouldRemoveNode;
|
||||
break;
|
||||
|
||||
case HtmlContentIntermediateNode htmlContentIntermediateNode:
|
||||
RemoveContiguousWhitespace(htmlContentIntermediateNode.Children, direction);
|
||||
shouldRemoveNode = htmlContentIntermediateNode.Children.Count == 0;
|
||||
shouldContinueIteration = shouldRemoveNode;
|
||||
break;
|
||||
|
||||
case HtmlElementIntermediateNode _:
|
||||
case CSharpExpressionIntermediateNode _:
|
||||
case TagHelperIntermediateNode _:
|
||||
// These node types may produce non-whitespace output at runtime
|
||||
shouldRemoveNode = false;
|
||||
shouldContinueIteration = false;
|
||||
break;
|
||||
|
||||
case CSharpCodeIntermediateNode codeIntermediateNode:
|
||||
shouldRemoveNode = false;
|
||||
shouldContinueIteration = ComponentDocumentClassifierPass.IsBuildRenderTreeBaseCall(codeIntermediateNode);
|
||||
break;
|
||||
|
||||
default:
|
||||
shouldRemoveNode = false;
|
||||
shouldContinueIteration = true; // Because other types of nodes don't produce output
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldRemoveNode)
|
||||
{
|
||||
nodes.RemoveAt(position);
|
||||
if (direction == TraversalDirection.Forwards)
|
||||
{
|
||||
position--;
|
||||
}
|
||||
}
|
||||
|
||||
position += direction == TraversalDirection.Forwards ? 1 : -1;
|
||||
|
||||
if (!shouldContinueIteration)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TraversalDirection
|
||||
{
|
||||
Forwards,
|
||||
Backwards
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal class TypeParamDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
|
||||
"typeparam",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.AddMemberToken(Resources.TypeParamDirective_Token_Name, Resources.TypeParamDirective_Token_Description);
|
||||
builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
|
||||
builder.Description = Resources.TypeParamDirective_Description;
|
||||
});
|
||||
|
||||
public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +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.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||
{
|
||||
// This is an unfortunate workaround due to https://github.com/aspnet/Razor/issues/2482
|
||||
// The Razor tooling looks for a type with exactly this name and will prevent tag helper
|
||||
// discovery if it is not found.
|
||||
//
|
||||
// This has to be its own assembly because we need to reference it in Blazor component libraries
|
||||
// in order for component discovery to work, but if we allow it as a reference for server-side
|
||||
// projects it will break MVC's features.
|
||||
internal class ITagHelper
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!-- This assembly ships in Components.Build -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Description>Workaround for a temporary Razor compiler issue needed by ASP.NET Core Components.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
// IMPORTANT
|
||||
//
|
||||
// Many of these names are used in code generation. Keep these in sync with the code generation code
|
||||
// See: src/Microsoft.AspNetCore.Components.Razor.Extensions/BlazorApi.cs
|
||||
// See: aspnet/AspNetCore-Tooling
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for building a collection of <see cref="RenderTreeFrame"/> entries.
|
||||
|
|
@ -264,7 +264,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
/// This method is provided for infrastructure purposes, and is used to be
|
||||
/// <see cref="UIEventArgsRenderTreeBuilderExtensions"/> to provide support for delegates of specific
|
||||
/// types. For a good programming experience when using a custom delegate type, define an
|
||||
/// extension method similar to
|
||||
/// extension method similar to
|
||||
/// <see cref="UIEventArgsRenderTreeBuilderExtensions.AddAttribute(RenderTreeBuilder, int, string, Action{UIChangeEventArgs})"/>
|
||||
/// that calls this method.
|
||||
/// </remarks>
|
||||
|
|
@ -280,7 +280,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
/// <summary>
|
||||
/// Appends a frame representing a string-valued attribute.
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c>, or
|
||||
/// the <see cref="System.Boolean" /> value <c>false</c> and the current element is not a component, the
|
||||
/// the <see cref="System.Boolean" /> value <c>false</c> and the current element is not a component, the
|
||||
/// frame will be omitted.
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\blazor\samples\StandaloneApp\StandaloneApp.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Components.Build\Microsoft.AspNetCore.Components.Build.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Components.Razor.Extensions\Microsoft.AspNetCore.Components.Razor.Extensions.csproj" />
|
||||
|
||||
<!-- Shared sources -->
|
||||
<Compile Include="..\shared\**\*.cs" Link="Helpers\%(Filename)%(Extension)" />
|
||||
|
|
|
|||
|
|
@ -1,53 +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.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public abstract class BaseTagHelperDescriptorProviderTest
|
||||
{
|
||||
static BaseTagHelperDescriptorProviderTest()
|
||||
{
|
||||
var dependencyContext = DependencyContext.Load(typeof(ComponentTagHelperDescriptorProviderTest).Assembly);
|
||||
|
||||
var metadataReferences = dependencyContext.CompileLibraries
|
||||
.SelectMany(l => l.ResolveReferencePaths())
|
||||
.Select(assemblyPath => MetadataReference.CreateFromFile(assemblyPath))
|
||||
.ToArray();
|
||||
|
||||
BaseCompilation = CSharpCompilation.Create(
|
||||
"TestAssembly",
|
||||
Array.Empty<SyntaxTree>(),
|
||||
metadataReferences,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
CSharpParseOptions = new CSharpParseOptions(LanguageVersion.CSharp7_3);
|
||||
}
|
||||
|
||||
protected static Compilation BaseCompilation { get; }
|
||||
|
||||
protected static CSharpParseOptions CSharpParseOptions { get; }
|
||||
|
||||
protected static CSharpSyntaxTree Parse(string text)
|
||||
{
|
||||
return (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(text, CSharpParseOptions);
|
||||
}
|
||||
|
||||
// For simplicity in testing, exclude the built-in components. We'll add more and we
|
||||
// don't want to update the tests when that happens.
|
||||
protected static TagHelperDescriptor[] ExcludeBuiltInComponents(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
return context.Results
|
||||
.Where(c => c.AssemblyName == "TestAssembly")
|
||||
.OrderBy(c => c.Name)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,683 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public class BindTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_FindsBindTagHelperOnComponentType_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : IComponent
|
||||
{
|
||||
public void Init(RenderHandle renderHandle) { }
|
||||
|
||||
public void SetParameters(ParameterCollection parameters) { }
|
||||
|
||||
[Parameter]
|
||||
string MyProperty { get; set; }
|
||||
|
||||
[Parameter]
|
||||
Action<string> MyPropertyChanged { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
// We run after component discovery and depend on the results.
|
||||
var componentProvider = new ComponentTagHelperDescriptorProvider();
|
||||
componentProvider.Execute(context);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
var bind = Assert.Single(matches);
|
||||
|
||||
// These are features Bind Tags Helpers don't use. Verifying them once here and
|
||||
// then ignoring them.
|
||||
Assert.Empty(bind.AllowedChildTags);
|
||||
Assert.Null(bind.TagOutputHint);
|
||||
|
||||
// These are features that are invariants of all Bind Tag Helpers. Verifying them once
|
||||
// here and then ignoring them.
|
||||
Assert.Empty(bind.Diagnostics);
|
||||
Assert.False(bind.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind);
|
||||
Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||
Assert.False(bind.IsDefaultKind());
|
||||
Assert.False(bind.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
Assert.Equal("MyProperty", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||
Assert.Equal("MyPropertyChanged", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to the 'MyProperty' property and a change event " +
|
||||
"delegate to the 'MyPropertyChanged' property of the component.",
|
||||
bind.Documentation);
|
||||
|
||||
// These are all trivially derived from the assembly/namespace/type name
|
||||
Assert.Equal("TestAssembly", bind.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent", bind.Name);
|
||||
Assert.Equal("Test.MyComponent", bind.DisplayName);
|
||||
Assert.Equal("Test.MyComponent", bind.GetTypeName());
|
||||
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Empty(rule.Diagnostics);
|
||||
Assert.False(rule.HasErrors);
|
||||
Assert.Null(rule.ParentTag);
|
||||
Assert.Equal("MyComponent", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Empty(requiredAttribute.Diagnostics);
|
||||
Assert.Equal("bind-MyProperty", requiredAttribute.DisplayName);
|
||||
Assert.Equal("bind-MyProperty", requiredAttribute.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||
Assert.Null(requiredAttribute.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes);
|
||||
|
||||
// Invariants
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.Null(attribute.IndexerNamePrefix);
|
||||
Assert.Null(attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to the 'MyProperty' property and a change event " +
|
||||
"delegate to the 'MyPropertyChanged' property of the component.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("bind-MyProperty", attribute.Name);
|
||||
Assert.Equal("MyProperty", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.MyComponent.MyProperty", attribute.DisplayName);
|
||||
|
||||
// Defined from the property type
|
||||
Assert.Equal("System.String", attribute.TypeName);
|
||||
Assert.True(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_NoMatchedPropertiesOnComponent_IgnoresComponent()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : IComponent
|
||||
{
|
||||
public void Init(RenderHandle renderHandle) { }
|
||||
|
||||
public void SetParameters(ParameterCollection parameters) { }
|
||||
|
||||
public string MyProperty { get; set; }
|
||||
|
||||
public Action<string> MyPropertyChangedNotMatch { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
// We run after component discovery and depend on the results.
|
||||
var componentProvider = new ComponentTagHelperDescriptorProvider();
|
||||
componentProvider.Execute(context);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_BindOnElement_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", null, ""myprop"", ""myevent"")]
|
||||
public class BindAttributes
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
var bind = Assert.Single(matches);
|
||||
|
||||
// These are features Bind Tags Helpers don't use. Verifying them once here and
|
||||
// then ignoring them.
|
||||
Assert.Empty(bind.AllowedChildTags);
|
||||
Assert.Null(bind.TagOutputHint);
|
||||
|
||||
// These are features that are invariants of all Bind Tag Helpers. Verifying them once
|
||||
// here and then ignoring them.
|
||||
Assert.Empty(bind.Diagnostics);
|
||||
Assert.False(bind.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind);
|
||||
Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||
Assert.False(bind.IsDefaultKind());
|
||||
Assert.False(bind.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
Assert.Equal("myprop", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||
Assert.Equal("myevent", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||
Assert.False(bind.IsInputElementBindTagHelper());
|
||||
Assert.False(bind.IsInputElementFallbackBindTagHelper());
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to the 'myprop' attribute and a change event " +
|
||||
"delegate to the 'myevent' attribute.",
|
||||
bind.Documentation);
|
||||
|
||||
// These are all trivially derived from the assembly/namespace/type name
|
||||
Assert.Equal("TestAssembly", bind.AssemblyName);
|
||||
Assert.Equal("Bind", bind.Name);
|
||||
Assert.Equal("Test.BindAttributes", bind.DisplayName);
|
||||
Assert.Equal("Test.BindAttributes", bind.GetTypeName());
|
||||
|
||||
// The tag matching rule for a bind-Component is always the component name + the attribute name
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Empty(rule.Diagnostics);
|
||||
Assert.False(rule.HasErrors);
|
||||
Assert.Null(rule.ParentTag);
|
||||
Assert.Equal("div", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Empty(requiredAttribute.Diagnostics);
|
||||
Assert.Equal("bind", requiredAttribute.DisplayName);
|
||||
Assert.Equal("bind", requiredAttribute.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||
Assert.Null(requiredAttribute.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("bind"));
|
||||
|
||||
// Invariants
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.Null(attribute.IndexerNamePrefix);
|
||||
Assert.Null(attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to the 'myprop' attribute and a change event " +
|
||||
"delegate to the 'myevent' attribute.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("bind", attribute.Name);
|
||||
Assert.Equal("Bind", attribute.GetPropertyName());
|
||||
Assert.Equal("object Test.BindAttributes.Bind", attribute.DisplayName);
|
||||
|
||||
// Defined from the property type
|
||||
Assert.Equal("System.Object", attribute.TypeName);
|
||||
Assert.False(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
|
||||
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format"));
|
||||
|
||||
// Invariants
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.Null(attribute.IndexerNamePrefix);
|
||||
Assert.Null(attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Equal(
|
||||
"Specifies a format to convert the value specified by the 'bind' attribute. " +
|
||||
"The format string can currently only be used with expressions of type <code>DateTime</code>.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("format-myprop", attribute.Name);
|
||||
Assert.Equal("Format_myprop", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.BindAttributes.Format_myprop", attribute.DisplayName);
|
||||
|
||||
// Defined from the property type
|
||||
Assert.Equal("System.String", attribute.TypeName);
|
||||
Assert.True(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_BindOnElementWithSuffix_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindElement(""div"", ""myprop"", ""myprop"", ""myevent"")]
|
||||
public class BindAttributes
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
var bind = Assert.Single(matches);
|
||||
|
||||
Assert.Equal("myprop", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||
Assert.Equal("myevent", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||
Assert.False(bind.IsInputElementBindTagHelper());
|
||||
Assert.False(bind.IsInputElementFallbackBindTagHelper());
|
||||
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Equal("div", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Equal("bind-myprop", requiredAttribute.DisplayName);
|
||||
Assert.Equal("bind-myprop", requiredAttribute.Name);
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("bind"));
|
||||
Assert.Equal("bind-myprop", attribute.Name);
|
||||
Assert.Equal("Bind_myprop", attribute.GetPropertyName());
|
||||
Assert.Equal("object Test.BindAttributes.Bind_myprop", attribute.DisplayName);
|
||||
|
||||
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format"));
|
||||
Assert.Equal("format-myprop", attribute.Name);
|
||||
Assert.Equal("Format_myprop", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.BindAttributes.Format_myprop", attribute.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_BindOnInputElementWithoutTypeAttribute_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindInputElement(null, null, ""myprop"", ""myevent"")]
|
||||
public class BindAttributes
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
var bind = Assert.Single(matches);
|
||||
|
||||
Assert.Equal("myprop", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||
Assert.Equal("myevent", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||
Assert.False(bind.Metadata.ContainsKey(BlazorMetadata.Bind.TypeAttribute));
|
||||
Assert.True(bind.IsInputElementBindTagHelper());
|
||||
Assert.True(bind.IsInputElementFallbackBindTagHelper());
|
||||
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Equal("input", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Equal("bind", requiredAttribute.DisplayName);
|
||||
Assert.Equal("bind", requiredAttribute.Name);
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("bind"));
|
||||
Assert.Equal("bind", attribute.Name);
|
||||
Assert.Equal("Bind", attribute.GetPropertyName());
|
||||
Assert.Equal("object Test.BindAttributes.Bind", attribute.DisplayName);
|
||||
|
||||
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format"));
|
||||
Assert.Equal("format-myprop", attribute.Name);
|
||||
Assert.Equal("Format_myprop", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.BindAttributes.Format_myprop", attribute.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_BindOnInputElementWithTypeAttribute_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindInputElement(""checkbox"", null, ""myprop"", ""myevent"")]
|
||||
public class BindAttributes
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
var bind = Assert.Single(matches);
|
||||
|
||||
Assert.Equal("myprop", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||
Assert.Equal("myevent", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||
Assert.Equal("checkbox", bind.Metadata[BlazorMetadata.Bind.TypeAttribute]);
|
||||
Assert.True(bind.IsInputElementBindTagHelper());
|
||||
Assert.False(bind.IsInputElementFallbackBindTagHelper());
|
||||
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Equal("input", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
Assert.Collection(
|
||||
rule.Attributes,
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("type", a.DisplayName);
|
||||
Assert.Equal("type", a.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, a.NameComparison);
|
||||
Assert.Equal("checkbox", a.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch, a.ValueComparison);
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("bind", a.DisplayName);
|
||||
Assert.Equal("bind", a.Name);
|
||||
});
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("bind"));
|
||||
Assert.Equal("bind", attribute.Name);
|
||||
Assert.Equal("Bind", attribute.GetPropertyName());
|
||||
Assert.Equal("object Test.BindAttributes.Bind", attribute.DisplayName);
|
||||
|
||||
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format"));
|
||||
Assert.Equal("format-myprop", attribute.Name);
|
||||
Assert.Equal("Format_myprop", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.BindAttributes.Format_myprop", attribute.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_BindOnInputElementWithTypeAttributeAndSuffix_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[BindInputElement(""checkbox"", ""somevalue"", ""myprop"", ""myevent"")]
|
||||
public class BindAttributes
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
var bind = Assert.Single(matches);
|
||||
|
||||
Assert.Equal("myprop", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||
Assert.Equal("myevent", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||
Assert.Equal("checkbox", bind.Metadata[BlazorMetadata.Bind.TypeAttribute]);
|
||||
Assert.True(bind.IsInputElementBindTagHelper());
|
||||
Assert.False(bind.IsInputElementFallbackBindTagHelper());
|
||||
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Equal("input", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
Assert.Collection(
|
||||
rule.Attributes,
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("type", a.DisplayName);
|
||||
Assert.Equal("type", a.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, a.NameComparison);
|
||||
Assert.Equal("checkbox", a.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch, a.ValueComparison);
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("bind-somevalue", a.DisplayName);
|
||||
Assert.Equal("bind-somevalue", a.Name);
|
||||
});
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("bind"));
|
||||
Assert.Equal("bind-somevalue", attribute.Name);
|
||||
Assert.Equal("Bind_somevalue", attribute.GetPropertyName());
|
||||
Assert.Equal("object Test.BindAttributes.Bind_somevalue", attribute.DisplayName);
|
||||
|
||||
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format"));
|
||||
Assert.Equal("format-somevalue", attribute.Name);
|
||||
Assert.Equal("Format_somevalue", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.BindAttributes.Format_somevalue", attribute.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_BindFallback_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation;
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var bind = Assert.Single(context.Results, r => r.IsFallbackBindTagHelper());
|
||||
|
||||
// These are features Bind Tags Helpers don't use. Verifying them once here and
|
||||
// then ignoring them.
|
||||
Assert.Empty(bind.AllowedChildTags);
|
||||
Assert.Null(bind.TagOutputHint);
|
||||
|
||||
// These are features that are invariants of all Bind Tag Helpers. Verifying them once
|
||||
// here and then ignoring them.
|
||||
Assert.Empty(bind.Diagnostics);
|
||||
Assert.False(bind.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind);
|
||||
Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||
Assert.False(bind.IsDefaultKind());
|
||||
Assert.False(bind.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
Assert.False(bind.Metadata.ContainsKey(BlazorMetadata.Bind.ValueAttribute));
|
||||
Assert.False(bind.Metadata.ContainsKey(BlazorMetadata.Bind.ChangeAttribute));
|
||||
Assert.True(bind.IsFallbackBindTagHelper());
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to an attribute and a change event, based on the naming of " +
|
||||
"the bind attribute. For example: <code>bind-value-onchange=\"...\"</code> will assign the " +
|
||||
"current value of the expression to the 'value' attribute, and assign a delegate that attempts " +
|
||||
"to set the value to the 'onchange' attribute.",
|
||||
bind.Documentation);
|
||||
|
||||
// These are all trivially derived from the assembly/namespace/type name
|
||||
Assert.Equal("Microsoft.AspNetCore.Components", bind.AssemblyName);
|
||||
Assert.Equal("Bind", bind.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Components.Bind", bind.DisplayName);
|
||||
Assert.Equal("Microsoft.AspNetCore.Components.Bind", bind.GetTypeName());
|
||||
|
||||
// The tag matching rule for a bind-Component is always the component name + the attribute name
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Empty(rule.Diagnostics);
|
||||
Assert.False(rule.HasErrors);
|
||||
Assert.Null(rule.ParentTag);
|
||||
Assert.Equal("*", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Empty(requiredAttribute.Diagnostics);
|
||||
Assert.Equal("bind-...", requiredAttribute.DisplayName);
|
||||
Assert.Equal("bind-", requiredAttribute.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch, requiredAttribute.NameComparison);
|
||||
Assert.Null(requiredAttribute.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("bind"));
|
||||
|
||||
// Invariants
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.True(attribute.HasIndexer);
|
||||
Assert.Equal("bind-", attribute.IndexerNamePrefix);
|
||||
Assert.Equal("System.Object", attribute.IndexerTypeName);
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to an attribute and a change event, based on the naming of " +
|
||||
"the bind attribute. For example: <code>bind-value-onchange=\"...\"</code> will assign the " +
|
||||
"current value of the expression to the 'value' attribute, and assign a delegate that attempts " +
|
||||
"to set the value to the 'onchange' attribute.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("bind-...", attribute.Name);
|
||||
Assert.Equal("Bind", attribute.GetPropertyName());
|
||||
Assert.Equal(
|
||||
"System.Collections.Generic.Dictionary<string, object> Microsoft.AspNetCore.Components.Bind.Bind",
|
||||
attribute.DisplayName);
|
||||
|
||||
// Defined from the property type
|
||||
Assert.Equal("System.Collections.Generic.Dictionary<string, object>", attribute.TypeName);
|
||||
Assert.False(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
|
||||
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format"));
|
||||
|
||||
// Invariants
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.True(attribute.HasIndexer);
|
||||
Assert.Equal("format-", attribute.IndexerNamePrefix);
|
||||
Assert.Equal("System.String", attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.True(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Equal(
|
||||
"Specifies a format to convert the value specified by the corresponding bind attribute. " +
|
||||
"For example: <code>format-value=\"...\"</code> will apply a format string to the value " +
|
||||
"specified in <code>bind-value-...</code>. The format string can currently only be used with " +
|
||||
"expressions of type <code>DateTime</code>.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("format-...", attribute.Name);
|
||||
Assert.Equal("Format", attribute.GetPropertyName());
|
||||
Assert.Equal(
|
||||
"System.Collections.Generic.Dictionary<string, string> Microsoft.AspNetCore.Components.Bind.Format",
|
||||
attribute.DisplayName);
|
||||
|
||||
// Defined from the property type
|
||||
Assert.Equal("System.Collections.Generic.Dictionary<string, string>", attribute.TypeName);
|
||||
Assert.False(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
|
||||
|
||||
private static TagHelperDescriptor[] GetBindTagHelpers(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
return ExcludeBuiltInComponents(context).Where(t => t.IsBindTagHelper()).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,476 +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;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Xunit;
|
||||
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public class ComponentDocumentRewritePassTest
|
||||
{
|
||||
public ComponentDocumentRewritePassTest()
|
||||
{
|
||||
var test = TagHelperDescriptorBuilder.Create("test", "test");
|
||||
test.TagMatchingRule(b => b.TagName = "test");
|
||||
|
||||
TagHelpers = new List<TagHelperDescriptor>()
|
||||
{
|
||||
test.Build(),
|
||||
};
|
||||
|
||||
Pass = new ComponentDocumentRewritePass();
|
||||
Engine = RazorProjectEngine.Create(
|
||||
BlazorExtensionInitializer.DefaultConfiguration,
|
||||
RazorProjectFileSystem.Create(Environment.CurrentDirectory),
|
||||
b =>
|
||||
{
|
||||
b.Features.Add(new ComponentDocumentClassifierPass());
|
||||
b.Features.Add(Pass);
|
||||
b.Features.Add(new StaticTagHelperFeature() { TagHelpers = TagHelpers, });
|
||||
}).Engine;
|
||||
}
|
||||
|
||||
private RazorEngine Engine { get; }
|
||||
|
||||
private ComponentDocumentRewritePass Pass { get; }
|
||||
|
||||
private List<TagHelperDescriptor> TagHelpers { get; }
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_Basic()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<head cool=""beans"">
|
||||
Hello, World!
|
||||
</head>
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "html"));
|
||||
|
||||
var html = NodeAssert.Element(method.Children[2], "html");
|
||||
Assert.Equal(2, html.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(1, html.Source.Value.LineIndex);
|
||||
Assert.Equal(0, html.Source.Value.CharacterIndex);
|
||||
Assert.Equal(68, html.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
html.Children,
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "head"),
|
||||
c => NodeAssert.Whitespace(c));
|
||||
|
||||
var head = NodeAssert.Element(html.Children[1], "head");
|
||||
Assert.Equal(12, head.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(2, head.Source.Value.LineIndex);
|
||||
Assert.Equal(2, head.Source.Value.CharacterIndex);
|
||||
Assert.Equal(49, head.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
head.Children,
|
||||
c => NodeAssert.Attribute(c, "cool", "beans"),
|
||||
c => NodeAssert.Content(c, "Hello, World!"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_Mixed()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<head cool=""beans"" csharp=""@yes"" mixed=""hi @there"">
|
||||
</head>
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "html"));
|
||||
|
||||
var html = NodeAssert.Element(method.Children[2], "html");
|
||||
Assert.Equal(2, html.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(1, html.Source.Value.LineIndex);
|
||||
Assert.Equal(0, html.Source.Value.CharacterIndex);
|
||||
Assert.Equal(81, html.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
html.Children,
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "head"),
|
||||
c => NodeAssert.Whitespace(c));
|
||||
|
||||
var head = NodeAssert.Element(html.Children[1], "head");
|
||||
Assert.Equal(12, head.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(2, head.Source.Value.LineIndex);
|
||||
Assert.Equal(2, head.Source.Value.CharacterIndex);
|
||||
Assert.Equal(62, head.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
head.Children,
|
||||
c => NodeAssert.Attribute(c, "cool", "beans"),
|
||||
c => NodeAssert.CSharpAttribute(c, "csharp", "yes"),
|
||||
c => Assert.IsType<HtmlAttributeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c));
|
||||
|
||||
var mixed = Assert.IsType<HtmlAttributeIntermediateNode>(head.Children[2]);
|
||||
Assert.Collection(
|
||||
mixed.Children,
|
||||
c => Assert.IsType<HtmlAttributeValueIntermediateNode>(c),
|
||||
c => Assert.IsType<CSharpExpressionAttributeValueIntermediateNode>(c));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_WithCode()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
@if (some_bool)
|
||||
{
|
||||
<head cool=""beans"">
|
||||
@hello
|
||||
</head>
|
||||
}
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "html"));
|
||||
|
||||
var html = NodeAssert.Element(method.Children[2], "html");
|
||||
Assert.Equal(2, html.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(1, html.Source.Value.LineIndex);
|
||||
Assert.Equal(0, html.Source.Value.CharacterIndex);
|
||||
Assert.Equal(90, html.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
html.Children,
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "head"),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c));
|
||||
|
||||
var head = NodeAssert.Element(html.Children[4], "head");
|
||||
Assert.Equal(36, head.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(4, head.Source.Value.LineIndex);
|
||||
Assert.Equal(2, head.Source.Value.CharacterIndex);
|
||||
Assert.Equal(42, head.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
head.Children,
|
||||
c => NodeAssert.Attribute(c, "cool", "beans"),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => Assert.IsType<CSharpExpressionIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_TagHelper()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
@addTagHelper ""*, test""
|
||||
<html>
|
||||
<test>
|
||||
<head cool=""beans"">
|
||||
Hello, World!
|
||||
</head>
|
||||
</test>
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => Assert.IsType<DirectiveIntermediateNode>(c),
|
||||
c => NodeAssert.Element(c, "html"));
|
||||
|
||||
var html = NodeAssert.Element(method.Children[3], "html");
|
||||
Assert.Equal(27, html.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(2, html.Source.Value.LineIndex);
|
||||
Assert.Equal(0, html.Source.Value.CharacterIndex);
|
||||
Assert.Equal(95, html.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
html.Children,
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => Assert.IsType<TagHelperIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c));
|
||||
|
||||
var body = html.Children
|
||||
.OfType<TagHelperIntermediateNode>().Single().Children
|
||||
.OfType<TagHelperBodyIntermediateNode>().Single();
|
||||
|
||||
Assert.Collection(
|
||||
body.Children,
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "head"),
|
||||
c => NodeAssert.Whitespace(c));
|
||||
|
||||
var head = body.Children[1];
|
||||
Assert.Equal(49, head.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(4, head.Source.Value.LineIndex);
|
||||
Assert.Equal(4, head.Source.Value.CharacterIndex);
|
||||
Assert.Equal(53, head.Source.Value.Length);
|
||||
Assert.Collection(
|
||||
head.Children,
|
||||
c => NodeAssert.Attribute(c, "cool", "beans"),
|
||||
c => NodeAssert.Content(c, "Hello, World!"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_UnbalancedClosing_MisuseOfVoidElement()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"<input></input>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Element(c, "input"),
|
||||
c => NodeAssert.Element(c, "input"));
|
||||
|
||||
var input2 = NodeAssert.Element(method.Children[2], "input");
|
||||
Assert.Equal(7, input2.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(0, input2.Source.Value.LineIndex);
|
||||
Assert.Equal(7, input2.Source.Value.CharacterIndex);
|
||||
Assert.Equal(8, input2.Source.Value.Length);
|
||||
|
||||
var diagnostic = Assert.Single(input2.Diagnostics);
|
||||
Assert.Same(BlazorDiagnosticFactory.UnexpectedClosingTagForVoidElement.Id, diagnostic.Id);
|
||||
Assert.Equal(input2.Source, diagnostic.Span);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_UnbalancedClosingTagAtTopLevel()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "html"));
|
||||
|
||||
var html = NodeAssert.Element(method.Children[2], "html");
|
||||
Assert.Equal(2, html.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(1, html.Source.Value.LineIndex);
|
||||
Assert.Equal(0, html.Source.Value.CharacterIndex);
|
||||
Assert.Equal(7, html.Source.Value.Length);
|
||||
|
||||
var diagnostic = Assert.Single(html.Diagnostics);
|
||||
Assert.Same(BlazorDiagnosticFactory.UnexpectedClosingTag.Id, diagnostic.Id);
|
||||
Assert.Equal(html.Source, diagnostic.Span);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_MismatchedClosingTag()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<div>
|
||||
</span>
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "html"));
|
||||
|
||||
var html = NodeAssert.Element(method.Children[2], "html");
|
||||
Assert.Collection(
|
||||
html.Children,
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "div"),
|
||||
c => NodeAssert.Whitespace(c));
|
||||
|
||||
var div = NodeAssert.Element(html.Children[1], "div");
|
||||
Assert.Equal(12, div.Source.Value.AbsoluteIndex);
|
||||
Assert.Equal(2, div.Source.Value.LineIndex);
|
||||
Assert.Equal(2, div.Source.Value.CharacterIndex);
|
||||
Assert.Equal(5, div.Source.Value.Length);
|
||||
|
||||
var diagnostic = Assert.Single(div.Diagnostics);
|
||||
Assert.Same(BlazorDiagnosticFactory.MismatchedClosingTag.Id, diagnostic.Id);
|
||||
Assert.Equal(21,diagnostic.Span.AbsoluteIndex);
|
||||
Assert.Equal(3, diagnostic.Span.LineIndex);
|
||||
Assert.Equal(2, diagnostic.Span.CharacterIndex);
|
||||
Assert.Equal(7, diagnostic.Span.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_MalformedHtmlAtEnd()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<ht");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Content(c, "<ht"));
|
||||
|
||||
var content = NodeAssert.Content(method.Children[2], "<ht");
|
||||
var diagnostic = Assert.Single(content.Diagnostics);
|
||||
Assert.Same(BlazorDiagnosticFactory.InvalidHtmlContent.Id, diagnostic.Id);
|
||||
Assert.Equal(2, diagnostic.Span.AbsoluteIndex);
|
||||
Assert.Equal(1, diagnostic.Span.LineIndex);
|
||||
Assert.Equal(0, diagnostic.Span.CharacterIndex);
|
||||
Assert.Equal(3, diagnostic.Span.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_UnclosedTags()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<div>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var method = documentNode.FindPrimaryMethod();
|
||||
Assert.Collection(
|
||||
method.Children,
|
||||
c => Assert.IsType<CSharpCodeIntermediateNode>(c),
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "html"));
|
||||
|
||||
var html = NodeAssert.Element(method.Children[2], "html");
|
||||
Assert.Collection(
|
||||
html.Children,
|
||||
c => NodeAssert.Whitespace(c),
|
||||
c => NodeAssert.Element(c, "div"));
|
||||
|
||||
var diagnostic = Assert.Single(html.Diagnostics);
|
||||
Assert.Same(BlazorDiagnosticFactory.UnclosedTag.Id, diagnostic.Id);
|
||||
Assert.Equal(2, diagnostic.Span.AbsoluteIndex);
|
||||
Assert.Equal(1, diagnostic.Span.LineIndex);
|
||||
Assert.Equal(0, diagnostic.Span.CharacterIndex);
|
||||
Assert.Equal(6, diagnostic.Span.Length);
|
||||
|
||||
var div = NodeAssert.Element(html.Children[1], "div");
|
||||
|
||||
diagnostic = Assert.Single(div.Diagnostics);
|
||||
Assert.Same(BlazorDiagnosticFactory.UnclosedTag.Id, diagnostic.Id);
|
||||
Assert.Equal(12, diagnostic.Span.AbsoluteIndex);
|
||||
Assert.Equal(2, diagnostic.Span.LineIndex);
|
||||
Assert.Equal(2, diagnostic.Span.CharacterIndex);
|
||||
Assert.Equal(5, diagnostic.Span.Length);
|
||||
}
|
||||
|
||||
private RazorCodeDocument CreateDocument(string content)
|
||||
{
|
||||
// Normalize newlines since we are testing lengths of things.
|
||||
content = content.Replace("\r", "");
|
||||
content = content.Replace("\n", "\r\n");
|
||||
|
||||
var source = RazorSourceDocument.Create(content, "test.cshtml");
|
||||
return RazorCodeDocument.Create(source);
|
||||
}
|
||||
|
||||
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
|
||||
{
|
||||
for (var i = 0; i < Engine.Phases.Count; i++)
|
||||
{
|
||||
var phase = Engine.Phases[i];
|
||||
if (phase is IRazorDocumentClassifierPhase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
phase.Execute(codeDocument);
|
||||
}
|
||||
|
||||
var document = codeDocument.GetDocumentIntermediateNode();
|
||||
Engine.Features.OfType<ComponentDocumentClassifierPass>().Single().Execute(codeDocument, document);
|
||||
return document;
|
||||
}
|
||||
|
||||
private class StaticTagHelperFeature : ITagHelperFeature
|
||||
{
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public List<TagHelperDescriptor> TagHelpers { get; set; }
|
||||
|
||||
public IReadOnlyList<TagHelperDescriptor> GetDescriptors()
|
||||
{
|
||||
return TagHelpers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,125 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public class EventHandlerTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_EventHandler_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[EventHandler(""onclick"", typeof(Action<UIMouseEventArgs>))]
|
||||
public class EventHandlers
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new EventHandlerTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetEventHandlerTagHelpers(context);
|
||||
var item = Assert.Single(matches);
|
||||
|
||||
// These are features Event Handler Tag Helpers don't use. Verifying them once here and
|
||||
// then ignoring them.
|
||||
Assert.Empty(item.AllowedChildTags);
|
||||
Assert.Null(item.TagOutputHint);
|
||||
|
||||
// These are features that are invariants of all Event Handler Helpers. Verifying them once
|
||||
// here and then ignoring them.
|
||||
Assert.Empty(item.Diagnostics);
|
||||
Assert.False(item.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.EventHandler.TagHelperKind, item.Kind);
|
||||
Assert.Equal(BlazorMetadata.EventHandler.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||
Assert.False(item.IsDefaultKind());
|
||||
Assert.False(item.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
Assert.Equal(
|
||||
"Sets the 'onclick' attribute to the provided string or delegate value. " +
|
||||
"A delegate value should be of type 'System.Action<Microsoft.AspNetCore.Components.UIMouseEventArgs>'.",
|
||||
item.Documentation);
|
||||
|
||||
// These are all trivially derived from the assembly/namespace/type name
|
||||
Assert.Equal("TestAssembly", item.AssemblyName);
|
||||
Assert.Equal("onclick", item.Name);
|
||||
Assert.Equal("Test.EventHandlers", item.DisplayName);
|
||||
Assert.Equal("Test.EventHandlers", item.GetTypeName());
|
||||
|
||||
// The tag matching rule for an event handler is just the attribute name
|
||||
var rule = Assert.Single(item.TagMatchingRules);
|
||||
Assert.Empty(rule.Diagnostics);
|
||||
Assert.False(rule.HasErrors);
|
||||
Assert.Null(rule.ParentTag);
|
||||
Assert.Equal("*", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Empty(requiredAttribute.Diagnostics);
|
||||
Assert.Equal("onclick", requiredAttribute.DisplayName);
|
||||
Assert.Equal("onclick", requiredAttribute.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||
Assert.Null(requiredAttribute.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||
|
||||
var attribute = Assert.Single(item.BoundAttributes);
|
||||
|
||||
// Invariants
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.EventHandler.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.Null(attribute.IndexerNamePrefix);
|
||||
Assert.Null(attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Collection(
|
||||
attribute.Metadata.OrderBy(kvp => kvp.Key),
|
||||
kvp => Assert.Equal(kvp, new KeyValuePair<string, string>(BlazorMetadata.Component.WeaklyTypedKey, bool.TrueString)),
|
||||
kvp => Assert.Equal(kvp, new KeyValuePair<string, string>("Common.PropertyName", "onclick")));
|
||||
|
||||
Assert.Equal(
|
||||
"Sets the 'onclick' attribute to the provided string or delegate value. " +
|
||||
"A delegate value should be of type 'System.Action<Microsoft.AspNetCore.Components.UIMouseEventArgs>'.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("onclick", attribute.Name);
|
||||
Assert.Equal("onclick", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.EventHandlers.onclick", attribute.DisplayName);
|
||||
|
||||
// Defined from the property type
|
||||
Assert.Equal("System.String", attribute.TypeName);
|
||||
Assert.True(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
|
||||
private static TagHelperDescriptor[] GetEventHandlerTagHelpers(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
return ExcludeBuiltInComponents(context).Where(t => t.IsEventHandlerTagHelper()).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +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.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public class GenericTypeNameRewriterTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("TItem2", "Type2")]
|
||||
|
||||
// Unspecified argument -> System.Object
|
||||
[InlineData("TItem3", "System.Object")]
|
||||
|
||||
// Not a type parameter
|
||||
[InlineData("TItem4", "TItem4")]
|
||||
|
||||
// In a qualified name, not a type parameter
|
||||
[InlineData("TItem1.TItem2", "TItem1.TItem2")]
|
||||
|
||||
// Type parameters can't have type parameters
|
||||
[InlineData("TItem1.TItem2<TItem1, TItem2, TItem3>", "TItem1.TItem2<Type1, Type2, System.Object>")]
|
||||
[InlineData("TItem2<TItem1<TItem3>, System.TItem2, RenderFragment<List<TItem1>>", "TItem2<TItem1<System.Object>, System.TItem2, RenderFragment<List<Type1>>")]
|
||||
public void GenericTypeNameRewriter_CanReplaceTypeParametersWithTypeArguments(string original, string expected)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new GenericTypeNameRewriter(new Dictionary<string, GenericTypeNameRewriter.Binding>()
|
||||
{
|
||||
{ "TItem1", new GenericTypeNameRewriter.Binding(){ Content = "Type1", } },
|
||||
{ "TItem2", new GenericTypeNameRewriter.Binding(){ Content = "Type2", } },
|
||||
{ "TItem3", new GenericTypeNameRewriter.Binding(){ Content = null, } },
|
||||
});
|
||||
|
||||
var parsed = SyntaxFactory.ParseTypeName(original);
|
||||
|
||||
// Act
|
||||
var actual = visitor.Visit(parsed);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +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.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public class GlobalQualifiedTypeNameRewriterTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("String", "global::String")]
|
||||
[InlineData("System.String", "global::System.String")]
|
||||
[InlineData("TItem2", "TItem2")]
|
||||
[InlineData("System.Collections.Generic.List<System.String>", "global::System.Collections.Generic.List<global::System.String>")]
|
||||
[InlineData("System.Collections.Generic.Dictionary<System.String, TItem1>", "global::System.Collections.Generic.Dictionary<global::System.String, TItem1>")]
|
||||
[InlineData("System.Collections.TItem3.Dictionary<System.String, TItem1>", "global::System.Collections.TItem3.Dictionary<global::System.String, TItem1>")]
|
||||
[InlineData("System.Collections.TItem3.TItem1<System.String, TItem1>", "global::System.Collections.TItem3.TItem1<global::System.String, TItem1>")]
|
||||
|
||||
// This case is interesting because we know TITem2 to be a generic type parameter,
|
||||
// and we know that this will never be valid, which is why we don't bother rewriting.
|
||||
[InlineData("TItem2<System.String, TItem1>", "TItem2<global::System.String, TItem1>")]
|
||||
public void GlobalQualifiedTypeNameRewriter_CanQualifyNames(string original, string expected)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new GlobalQualifiedTypeNameRewriter(new[] { "TItem1", "TItem2", "TItem3" });
|
||||
|
||||
var parsed = SyntaxFactory.ParseTypeName(original);
|
||||
|
||||
// Act
|
||||
var actual = visitor.Visit(parsed);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,385 +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;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Xunit;
|
||||
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public class HtmlBlockPassTest
|
||||
{
|
||||
public HtmlBlockPassTest()
|
||||
{
|
||||
Pass = new HtmlBlockPass();
|
||||
Engine = RazorProjectEngine.Create(
|
||||
BlazorExtensionInitializer.DefaultConfiguration,
|
||||
RazorProjectFileSystem.Create(Environment.CurrentDirectory),
|
||||
b =>
|
||||
{
|
||||
BlazorExtensionInitializer.Register(b);
|
||||
|
||||
if (b.Features.OfType<HtmlBlockPass>().Any())
|
||||
{
|
||||
b.Features.Remove(b.Features.OfType<HtmlBlockPass>().Single());
|
||||
}
|
||||
}).Engine;
|
||||
|
||||
Pass.Engine = Engine;
|
||||
}
|
||||
|
||||
private RazorEngine Engine { get; }
|
||||
|
||||
private HtmlBlockPass Pass { get; }
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_Basic()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<head cool=""beans"">
|
||||
Hello, World!
|
||||
</head>
|
||||
</html>");
|
||||
|
||||
var expected = NormalizeContent(@"
|
||||
<html>
|
||||
<head cool=""beans"">
|
||||
Hello, World!
|
||||
</head>
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_WithComment()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"Start<!-- -->End");
|
||||
|
||||
var expected = NormalizeContent(@"StartEnd");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_MergesSiblings()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
@(""Hi"")<div></div>
|
||||
<div></div>
|
||||
<div>@(""Hi"")</div>
|
||||
</html>");
|
||||
|
||||
var expected = NormalizeContent(@"
|
||||
<div></div>
|
||||
<div></div>
|
||||
");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_MergesSiblings_LeftEdge()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html><div></div>
|
||||
<div></div>
|
||||
<div>@(""Hi"")</div>
|
||||
</html>");
|
||||
|
||||
var expected = NormalizeContent(@"
|
||||
<div></div>
|
||||
<div></div>
|
||||
");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_CSharpInAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<head cool=""beans"" csharp=""@yes"" mixed=""hi @there"">
|
||||
<div>foo</div>
|
||||
</head>
|
||||
</html>");
|
||||
|
||||
var expected = NormalizeContent("<div>foo</div>\n ");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_CSharpInBody()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<head cool=""beans"">
|
||||
<div>@foo</div>
|
||||
<div>rewriteme</div>
|
||||
<div>@bar</div>
|
||||
</head>
|
||||
</html>");
|
||||
|
||||
var expected = NormalizeContent("<div>rewriteme</div>\n ");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_EncodesHtmlEntities()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<div>
|
||||
<span>Hi</span>
|
||||
</div>");
|
||||
|
||||
var expected = NormalizeContent(@"
|
||||
<div>
|
||||
<span>Hi</span>
|
||||
</div>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_EmptyNonvoid()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"<a href=""...""></a>");
|
||||
|
||||
var expected = NormalizeContent(@"<a href=""...""></a>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_Void()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"<link rel=""..."" href=""...""/>");
|
||||
|
||||
var expected = NormalizeContent(@"<link rel=""..."" href=""..."">");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_CannotRewriteHtml_CSharpInCode()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
@if (some_bool)
|
||||
{
|
||||
<head cool=""beans"">
|
||||
@hello
|
||||
</head>
|
||||
}
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_CannotRewriteHtml_Script()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
@if (some_bool)
|
||||
{
|
||||
<head cool=""beans"">
|
||||
<script>...</script>
|
||||
</head>
|
||||
}
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>());
|
||||
}
|
||||
|
||||
// The unclosed tag will have errors, so we won't rewrite it or its parent.
|
||||
[Fact]
|
||||
public void Execute_CannotRewriteHtml_Errors()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<a href=""..."">
|
||||
</html>");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RewritesHtml_MismatchedClosingTag()
|
||||
{
|
||||
// Arrange
|
||||
var document = CreateDocument(@"
|
||||
<html>
|
||||
<div>
|
||||
<div>rewriteme</div>
|
||||
</span>
|
||||
</html>");
|
||||
|
||||
var expected = NormalizeContent("<div>rewriteme</div>\n ");
|
||||
|
||||
var documentNode = Lower(document);
|
||||
|
||||
// Act
|
||||
Pass.Execute(document, documentNode);
|
||||
|
||||
// Assert
|
||||
var block = documentNode.FindDescendantNodes<HtmlBlockIntermediateNode>().Single();
|
||||
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
private string NormalizeContent(string content)
|
||||
{
|
||||
// Test inputs frequently have leading space for readability.
|
||||
content = content.TrimStart();
|
||||
|
||||
// Normalize newlines since we are testing lengths of things.
|
||||
content = content.Replace("\r", "");
|
||||
content = content.Replace("\n", "\r\n");
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private RazorCodeDocument CreateDocument(string content)
|
||||
{
|
||||
// Normalize newlines since we are testing lengths of things.
|
||||
content = content.Replace("\r", "");
|
||||
content = content.Replace("\n", "\r\n");
|
||||
|
||||
var source = RazorSourceDocument.Create(content, "test.cshtml");
|
||||
return RazorCodeDocument.Create(source);
|
||||
}
|
||||
|
||||
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
|
||||
{
|
||||
for (var i = 0; i < Engine.Phases.Count; i++)
|
||||
{
|
||||
var phase = Engine.Phases[i];
|
||||
if (phase is IRazorCSharpLoweringPhase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
phase.Execute(codeDocument);
|
||||
}
|
||||
|
||||
var document = codeDocument.GetDocumentIntermediateNode();
|
||||
Engine.Features.OfType<ComponentDocumentClassifierPass>().Single().Execute(codeDocument, document);
|
||||
return document;
|
||||
}
|
||||
|
||||
private class StaticTagHelperFeature : ITagHelperFeature
|
||||
{
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public List<TagHelperDescriptor> TagHelpers { get; set; }
|
||||
|
||||
public IReadOnlyList<TagHelperDescriptor> GetDescriptors()
|
||||
{
|
||||
return TagHelpers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
<!--
|
||||
Retains compilation settings so we can create a compilation during tests with the same data
|
||||
used to compile this assembly.
|
||||
-->
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Components.Razor.Extensions\Microsoft.AspNetCore.Components.Razor.Extensions.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Components\Microsoft.AspNetCore.Components.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,126 +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.Text;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
internal static class NodeAssert
|
||||
{
|
||||
public static HtmlAttributeIntermediateNode Attribute(IntermediateNode node, string attributeName, string attributeValue)
|
||||
{
|
||||
Assert.NotNull(node);
|
||||
|
||||
var attributeNode = Assert.IsType<HtmlAttributeIntermediateNode>(node);
|
||||
Assert.Equal(attributeName, attributeNode.AttributeName);
|
||||
|
||||
var attributeValueNode = Assert.IsType<HtmlAttributeValueIntermediateNode>(Assert.Single(attributeNode.Children));
|
||||
var actual = new StringBuilder();
|
||||
for (var i = 0; i < attributeValueNode.Children.Count; i++)
|
||||
{
|
||||
var token = Assert.IsType<IntermediateToken>(attributeValueNode.Children[i]);
|
||||
Assert.Equal(TokenKind.Html, token.Kind);
|
||||
actual.Append(token.Content);
|
||||
}
|
||||
|
||||
Assert.Equal(attributeValue, actual.ToString());
|
||||
|
||||
return attributeNode;
|
||||
}
|
||||
|
||||
public static HtmlAttributeIntermediateNode Attribute(IntermediateNodeCollection nodes, string attributeName, string attributeValue)
|
||||
{
|
||||
Assert.NotNull(nodes);
|
||||
return Attribute(Assert.Single(nodes), attributeName, attributeValue);
|
||||
}
|
||||
|
||||
public static HtmlContentIntermediateNode Content(IntermediateNode node, string content, bool trim = true)
|
||||
{
|
||||
Assert.NotNull(node);
|
||||
|
||||
var contentNode = Assert.IsType<HtmlContentIntermediateNode>(node);
|
||||
|
||||
var actual = new StringBuilder();
|
||||
for (var i = 0; i < contentNode.Children.Count; i++)
|
||||
{
|
||||
var token = Assert.IsType<IntermediateToken>(contentNode.Children[i]);
|
||||
Assert.Equal(TokenKind.Html, token.Kind);
|
||||
actual.Append(token.Content);
|
||||
}
|
||||
|
||||
Assert.Equal(content, trim ? actual.ToString().Trim() : actual.ToString());
|
||||
return contentNode;
|
||||
}
|
||||
|
||||
public static HtmlContentIntermediateNode Content(IntermediateNodeCollection nodes, string content, bool trim = true)
|
||||
{
|
||||
Assert.NotNull(nodes);
|
||||
return Content(Assert.Single(nodes), content, trim);
|
||||
}
|
||||
|
||||
public static HtmlAttributeIntermediateNode CSharpAttribute(IntermediateNode node, string attributeName, string attributeValue)
|
||||
{
|
||||
Assert.NotNull(node);
|
||||
|
||||
var attributeNode = Assert.IsType<HtmlAttributeIntermediateNode>(node);
|
||||
Assert.Equal(attributeName, attributeNode.AttributeName);
|
||||
|
||||
var attributeValueNode = Assert.IsType<CSharpExpressionAttributeValueIntermediateNode>(Assert.Single(attributeNode.Children));
|
||||
var actual = new StringBuilder();
|
||||
for (var i = 0; i < attributeValueNode.Children.Count; i++)
|
||||
{
|
||||
var token = Assert.IsType<IntermediateToken>(attributeValueNode.Children[i]);
|
||||
Assert.Equal(TokenKind.CSharp, token.Kind);
|
||||
actual.Append(token.Content);
|
||||
}
|
||||
|
||||
Assert.Equal(attributeValue, actual.ToString());
|
||||
|
||||
return attributeNode;
|
||||
}
|
||||
|
||||
public static HtmlAttributeIntermediateNode CSharpAttribute(IntermediateNodeCollection nodes, string attributeName, string attributeValue)
|
||||
{
|
||||
Assert.NotNull(nodes);
|
||||
return Attribute(Assert.Single(nodes), attributeName, attributeValue);
|
||||
}
|
||||
|
||||
public static HtmlElementIntermediateNode Element(IntermediateNode node, string tagName)
|
||||
{
|
||||
Assert.NotNull(node);
|
||||
|
||||
var elementNode = Assert.IsType<HtmlElementIntermediateNode>(node);
|
||||
Assert.Equal(tagName, elementNode.TagName);
|
||||
return elementNode;
|
||||
}
|
||||
|
||||
public static HtmlElementIntermediateNode Element(IntermediateNodeCollection nodes, string tagName)
|
||||
{
|
||||
Assert.NotNull(nodes);
|
||||
return Element(Assert.Single(nodes), tagName);
|
||||
}
|
||||
|
||||
public static HtmlContentIntermediateNode Whitespace(IntermediateNode node)
|
||||
{
|
||||
Assert.NotNull(node);
|
||||
|
||||
var contentNode = Assert.IsType<HtmlContentIntermediateNode>(node);
|
||||
for (var i = 0; i < contentNode.Children.Count; i++)
|
||||
{
|
||||
var token = Assert.IsType<IntermediateToken>(contentNode.Children[i]);
|
||||
Assert.Equal(TokenKind.Html, token.Kind);
|
||||
Assert.True(string.IsNullOrWhiteSpace(token.Content));
|
||||
}
|
||||
|
||||
return contentNode;
|
||||
}
|
||||
|
||||
public static HtmlContentIntermediateNode Whitespace(IntermediateNodeCollection nodes)
|
||||
{
|
||||
Assert.NotNull(nodes);
|
||||
return Whitespace(Assert.Single(nodes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +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.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Razor
|
||||
{
|
||||
public class RefTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
var provider = new RefTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = context.Results.Where(result => result.IsRefTagHelper());
|
||||
var item = Assert.Single(matches);
|
||||
|
||||
Assert.Empty(item.AllowedChildTags);
|
||||
Assert.Null(item.TagOutputHint);
|
||||
Assert.Empty(item.Diagnostics);
|
||||
Assert.False(item.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Ref.TagHelperKind, item.Kind);
|
||||
Assert.Equal(BlazorMetadata.Ref.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||
Assert.False(item.IsDefaultKind());
|
||||
Assert.False(item.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
Assert.Equal(
|
||||
"Populates the specified field or property with a reference to the element or component.",
|
||||
item.Documentation);
|
||||
|
||||
Assert.Equal("Microsoft.AspNetCore.Components", item.AssemblyName);
|
||||
Assert.Equal("Ref", item.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Components.Ref", item.DisplayName);
|
||||
Assert.Equal("Microsoft.AspNetCore.Components.Ref", item.GetTypeName());
|
||||
|
||||
// The tag matching rule for a ref is just the attribute name "ref"
|
||||
var rule = Assert.Single(item.TagMatchingRules);
|
||||
Assert.Empty(rule.Diagnostics);
|
||||
Assert.False(rule.HasErrors);
|
||||
Assert.Null(rule.ParentTag);
|
||||
Assert.Equal("*", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Empty(requiredAttribute.Diagnostics);
|
||||
Assert.Equal("ref", requiredAttribute.DisplayName);
|
||||
Assert.Equal("ref", requiredAttribute.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||
Assert.Null(requiredAttribute.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||
|
||||
var attribute = Assert.Single(item.BoundAttributes);
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Ref.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.Null(attribute.IndexerNamePrefix);
|
||||
Assert.Null(attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Equal(
|
||||
"Populates the specified field or property with a reference to the element or component.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("ref", attribute.Name);
|
||||
Assert.Equal("Ref", attribute.GetPropertyName());
|
||||
Assert.Equal("object Microsoft.AspNetCore.Components.Ref.Ref", attribute.DisplayName);
|
||||
Assert.Equal("System.Object", attribute.TypeName);
|
||||
Assert.False(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"shadowCopy": false
|
||||
}
|
||||
Loading…
Reference in New Issue