diff --git a/src/Razor/Directory.Build.targets b/src/Razor/Directory.Build.targets
index 78626b773e..181bfc9f70 100644
--- a/src/Razor/Directory.Build.targets
+++ b/src/Razor/Directory.Build.targets
@@ -1,8 +1,8 @@
- $(MicrosoftNETCoreApp20PackageVersion)
$(MicrosoftNETCoreApp21PackageVersion)
$(MicrosoftNETCoreApp22PackageVersion)
+ $(MicrosoftNETCoreApp30PackageVersion)
$(NETStandardLibrary20PackageVersion)
99.9
diff --git a/src/Razor/NuGetPackageVerifier.json b/src/Razor/NuGetPackageVerifier.json
index b62352e8b9..ceaad8c670 100644
--- a/src/Razor/NuGetPackageVerifier.json
+++ b/src/Razor/NuGetPackageVerifier.json
@@ -18,54 +18,53 @@
"DefaultCompositeRule"
],
"packages": {
- "Microsoft.AspNetCore.Razor.Design": {
- "exclusions": {
+ "Microsoft.NET.Sdk.Razor": {
+ "Exclusions": {
"ASSEMBLY_DESCRIPTION": {
- "tools/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions."
- },
- "NEUTRAL_RESOURCES_LANGUAGE": {
- "tools/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions."
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions."
},
"ASSEMBLY_PRODUCT": {
- "tools/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions."
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions."
+ },
+ "NEUTRAL_RESOURCES_LANGUAGE": {
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions."
},
"SERVICING_ATTRIBUTE": {
- "tools/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions."
- },
- "VERSION_INFORMATIONALVERSION": {
- "tools/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions."
+ "tools/netcoreapp3.0/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions."
},
"WRONG_PUBLICKEYTOKEN": {
- "tools/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
},
"ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": {
- "tools/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
},
"ASSEMBLY_FILE_VERSION_MISMATCH": {
- "tools/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
},
"ASSEMBLY_VERSION_MISMATCH": {
- "tools/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
- "tools/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.CSharp.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Microsoft.CodeAnalysis.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/Newtonsoft.Json.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/unix/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions.",
+ "tools/netcoreapp3.0/runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll": "This assembly is not owned by us and does not follow our conventions."
+ },
+ "BUILD_ITEMS_FRAMEWORK": {
+ "*": "Razor SDK does not contain any dependencies or binaries and consequently does not have a dependency group."
}
}
}
diff --git a/src/Razor/Razor.sln b/src/Razor/Razor.sln
index 5bb3137be2..8232e2316c 100644
--- a/src/Razor/Razor.sln
+++ b/src/Razor/Razor.sln
@@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
build\dependencies.props = build\dependencies.props
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
+ build\repo.props = build\repo.props
+ build\repo.targets = build\repo.targets
build\sources.props = build\sources.props
EndProjectSection
EndProject
@@ -77,9 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Mac.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test", "test\Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test\Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test.csproj", "{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Design", "src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj", "{5257B25D-330A-4DCF-ACED-B4709CFBF916}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Design.Test", "test\Microsoft.AspNetCore.Razor.Design.Test\Microsoft.AspNetCore.Razor.Design.Test.csproj", "{1D90F276-E1CA-4FDF-A173-EB889E7D3150}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Razor.Test", "test\Microsoft.NET.Sdk.Razor.Test\Microsoft.NET.Sdk.Razor.Test.csproj", "{1D90F276-E1CA-4FDF-A173-EB889E7D3150}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Test", "test\Microsoft.AspNetCore.Razor.Test\Microsoft.AspNetCore.Razor.Test.csproj", "{323553F0-14AB-4FBD-9CF0-1CC0BE8056F8}"
EndProject
@@ -97,6 +97,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib", "test\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib.csproj", "{72E89155-86C7-454E-BDD9-39F497F2F61B}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X", "src\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj", "{0693CA32-BB75-401E-BC08-72D6DEEB4C99}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.Test", "test\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.Test\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.Test.csproj", "{495A006C-D2B9-4AD0-9D33-60820BA501D8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Test.MvcShim.Version2_X", "test\Microsoft.AspNetCore.Razor.Test.MvcShim.Version2_X\Microsoft.AspNetCore.Razor.Test.MvcShim.Version2_X.csproj", "{D87E5501-B832-46B6-ACD3-EC989E3D14ED}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -327,14 +333,6 @@ Global
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}.Release|Any CPU.Build.0 = Release|Any CPU
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.Release|Any CPU.Build.0 = Release|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
- {5257B25D-330A-4DCF-ACED-B4709CFBF916}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
@@ -399,6 +397,30 @@ Global
{72E89155-86C7-454E-BDD9-39F497F2F61B}.Release|Any CPU.Build.0 = Release|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{72E89155-86C7-454E-BDD9-39F497F2F61B}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -432,7 +454,6 @@ Global
{FAF9986F-E086-4513-9D52-F7BF5FFCF31D} = {C0CC1E1F-1559-44DE-93A8-63259CEA2AAB}
{95B18DEE-8B45-4CF0-B9F8-CCBAF3B5251A} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042} = {92463391-81BE-462B-AC3C-78C6C760741F}
- {5257B25D-330A-4DCF-ACED-B4709CFBF916} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{1D90F276-E1CA-4FDF-A173-EB889E7D3150} = {92463391-81BE-462B-AC3C-78C6C760741F}
{323553F0-14AB-4FBD-9CF0-1CC0BE8056F8} = {92463391-81BE-462B-AC3C-78C6C760741F}
{6205467F-E381-4C42-AEEC-763BD62B3D5E} = {C2C98051-0F39-47F2-80B6-E72B29159F2C}
@@ -441,6 +462,9 @@ Global
{7D9ECCEE-71D1-4A42-ABEE-876AFA1B4FC9} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{6EA56B2B-89EC-4C38-A384-97D203375B06} = {92463391-81BE-462B-AC3C-78C6C760741F}
{72E89155-86C7-454E-BDD9-39F497F2F61B} = {92463391-81BE-462B-AC3C-78C6C760741F}
+ {0693CA32-BB75-401E-BC08-72D6DEEB4C99} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
+ {495A006C-D2B9-4AD0-9D33-60820BA501D8} = {92463391-81BE-462B-AC3C-78C6C760741F}
+ {D87E5501-B832-46B6-ACD3-EC989E3D14ED} = {92463391-81BE-462B-AC3C-78C6C760741F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}
diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs
index 70590a6a35..580ddd1572 100644
--- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs
+++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs
@@ -132,14 +132,6 @@ namespace Microsoft.AspNetCore.Razor.Performance
private class StaticProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory
{
- public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure)
- {
- return RazorProjectEngine.Create(project.Configuration, fileSystem, b =>
- {
- RazorExtensions.Register(b);
- });
- }
-
public override IProjectEngineFactory FindFactory(ProjectSnapshot project)
{
throw new NotImplementedException();
@@ -149,6 +141,14 @@ namespace Microsoft.AspNetCore.Razor.Performance
{
throw new NotImplementedException();
}
+
+ public override RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ RazorExtensions.Register(b);
+ });
+ }
}
}
}
diff --git a/src/Razor/build/MPack.targets b/src/Razor/build/MPack.targets
index ce81b91622..e00b58a968 100644
--- a/src/Razor/build/MPack.targets
+++ b/src/Razor/build/MPack.targets
@@ -7,9 +7,9 @@
shipoob
$(IntermediateDir)mpack\
- $(AddinDirectory)bin\$(Configuration)\net461\
+ $(AddinDirectory)bin\$(Configuration)\net472\
Microsoft.VisualStudio.Mac.LanguageServices.Razor
- $(RepositoryRoot)src\$(LanguageServiceName)\bin\$(Configuration)\net461\
+ $(RepositoryRoot)src\$(LanguageServiceName)\bin\$(Configuration)\net472\
$(AddinName)_$(AddinVersion)
$(MPackName).mpack
$(BuildDir)$(MPackFileName)
@@ -72,8 +72,9 @@
-
-
+
+
+
diff --git a/src/Razor/build/VSIX.targets b/src/Razor/build/VSIX.targets
index eda19a3bef..bc3460fd16 100644
--- a/src/Razor/build/VSIX.targets
+++ b/src/Razor/build/VSIX.targets
@@ -105,7 +105,7 @@
/p:Configuration=$(Configuration);
/p:FeatureBranchVersionSuffix=$(FeatureBranchVersionSuffix);
/p:BuildNumber=$(BuildNumber);
- /p:LangVersion=7.1" />
+ /p:LangVersion=7.2" />
diff --git a/src/Razor/build/dependencies.props b/src/Razor/build/dependencies.props
index b76ea87d30..983109a3be 100644
--- a/src/Razor/build/dependencies.props
+++ b/src/Razor/build/dependencies.props
@@ -4,60 +4,63 @@
0.10.13
- 2.2.0-preview2-20181108.4
- 2.2.0-rtm-181106-13
- 2.2.0-rtm-35661
- 2.2.0-rtm-181106-13
- 15.6.82
- 15.6.82
- 15.6.82
+ 3.0.0-alpha1-20181108.5
+ 3.0.0-preview-181106-14
+ 3.0.0-alpha1-10717
+ 3.0.0-preview-181106-14
+ 15.8.166
+ 15.8.166
+ 15.8.166
2.8.0
2.8.0
- 2.2.0-rtm-181106-13
- 2.2.0-rtm-181106-13
- 2.1.0
- 2.2.0-rtm-181106-13
- 2.2.0-rtm-181106-13
- 2.2.0-rtm-35661
- 2.0.9
+ 3.0.0-preview-181106-14
+ 3.0.0-preview-181106-14
+ 3.0.0-preview1-26907-05
+ 3.0.0-preview-181106-14
+ 3.0.0-preview-181106-14
+ 3.0.0-alpha1-10717
2.1.3
- 2.2.0-rtm-27105-02
+ 3.0.0-preview1-26907-05
+ 1.0.0-alpha-004
15.6.1
- 15.0.26606
- 15.6.161-preview
- 15.6.161-preview
- 7.10.6070
- 15.3.224
+ 15.8.525
+ 15.8.28010
+ 16.0.142-g25b7188c54
+ 16.0.142-g25b7188c54
+ 16.0.142-g25b7188c54
+ 7.10.6071
+ 16.0.201-pre-g7d366164d0
2.0.6142705
- 15.3.224
- 15.0.26606
- 10.0.30319
- 11.0.61030
- 12.0.30110
- 8.0.50727
- 9.0.30729
- 7.10.6071
- 15.6.161-preview
+ 16.0.201-pre-g7d366164d0
+ 15.8.28010
+ 10.0.30320
+ 11.0.61031
+ 12.0.30111
+ 8.0.50728
+ 9.0.30730
+ 7.10.6072
+ 16.0.142-g25b7188c54
+ 15.8.168
1.3.8
1.0.1
4.10.0
2.0.3
11.0.2
- 1.1.92
- 4.5.0
+ 1.3.23
+ 4.6.0-preview1-26907-04
4.3.0
- 4.5.0
+ 4.6.0-preview1-26829-04
9.0.1
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
- 2.9.0-beta4-62911-02
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
+ 2.11.0-beta3-63519-01
0.10.0
2.3.1
2.4.0
diff --git a/src/Razor/build/repo.props b/src/Razor/build/repo.props
index def34f0b6f..0aebc758fb 100644
--- a/src/Razor/build/repo.props
+++ b/src/Razor/build/repo.props
@@ -21,13 +21,11 @@
Internal.AspNetCore.Universe.Lineup
- 2.2.0-*
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json
-
-
+
diff --git a/src/Razor/build/repo.targets b/src/Razor/build/repo.targets
index 550069ec47..1c111a1c01 100644
--- a/src/Razor/build/repo.targets
+++ b/src/Razor/build/repo.targets
@@ -11,9 +11,9 @@
$(PrepareDependsOn);GenerateMSBuildLocationFile
- $(RepositoryRoot)test\Microsoft.AspNetCore.Razor.Design.Test\
- $(RazorDesignTestProject)BuildVariables.cs.template
- $(RazorDesignTestProject)obj\BuildVariables.generated.cs
+ $(RepositoryRoot)test\Microsoft.NET.Sdk.Razor.Test\
+ $(RazorSdkTestProject)BuildVariables.cs.template
+ $(RazorSdkTestProject)obj\BuildVariables.generated.cs
MSBuildLocation=$(VisualStudioMSBuildx86Path);
- MicrosoftNETCoreAppPackageVersion=$(MicrosoftNETCoreApp22PackageVersion);
- NETStandardLibraryPackageVersion=$(NETStandardLibrary20PackageVersion)
+ MicrosoftNETCoreApp30PackageVersion=$(MicrosoftNETCoreApp30PackageVersion);
+ NETStandardLibrary20PackageVersion=$(NETStandardLibrary20PackageVersion)
diff --git a/src/Razor/build/sources.props b/src/Razor/build/sources.props
index 02efac4549..2bd61d3599 100644
--- a/src/Razor/build/sources.props
+++ b/src/Razor/build/sources.props
@@ -10,8 +10,10 @@
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
https://dotnet.myget.org/F/msbuild/api/v3/index.json;
https://dotnet.myget.org/F/roslyn/api/v3/index.json;
+ https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json;
https://vside.myget.org/F/vssdk/api/v3/index.json;
- https://vside.myget.org/F/vsmac/api/v3/index.json
+ https://vside.myget.org/F/vsmac/api/v3/index.json;
+ https://vside.myget.org/F/devcore/api/v3/index.json;
$(RestoreSources);
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj
index c6b3d3e212..0d61d12b26 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj
@@ -2,7 +2,7 @@
ASP.NET Core design time hosting infrastructure for the Razor view engine.
- net46;netstandard2.0
+ netstandard2.0
$(PackageTags);aspnetcoremvc
false
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
index 5211072d0e..55e8929457 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
@@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
InjectDirective.Register(builder);
ModelDirective.Register(builder);
- FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
builder.Features.Add(new DefaultTagHelperDescriptorProvider());
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/AssemblyAttributeInjectionPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/AssemblyAttributeInjectionPass.cs
new file mode 100644
index 0000000000..021c61c368
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/AssemblyAttributeInjectionPass.cs
@@ -0,0 +1,102 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Diagnostics;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class AssemblyAttributeInjectionPass : IntermediateNodePassBase, IRazorOptimizationPass
+ {
+ private const string RazorViewAttribute = "global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute";
+ private const string RazorPageAttribute = "global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute";
+
+ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ if (documentNode.Options.DesignTime)
+ {
+ return;
+ }
+
+ var @namespace = documentNode.FindPrimaryNamespace();
+ if (@namespace == null || string.IsNullOrEmpty(@namespace.Content))
+ {
+ // No namespace node or it's incomplete. Skip.
+ return;
+ }
+
+ var @class = documentNode.FindPrimaryClass();
+ if (@class == null || string.IsNullOrEmpty(@class.ClassName))
+ {
+ // No class node or it's incomplete. Skip.
+ return;
+ }
+
+ var generatedTypeName = $"{@namespace.Content}.{@class.ClassName}";
+
+ // The MVC attributes require a relative path to be specified so that we can make a view engine path.
+ // We can't use a rooted path because we don't know what the project root is.
+ //
+ // If we can't sanitize the path, we'll just set it to null and let is blow up at runtime - we don't
+ // want to create noise if this code has to run in some unanticipated scenario.
+ var escapedPath = MakeVerbatimStringLiteral(ConvertToViewEnginePath(codeDocument.Source.RelativePath));
+
+ string attribute;
+ if (documentNode.DocumentKind == MvcViewDocumentClassifierPass.MvcViewDocumentKind)
+ {
+ attribute = $"[assembly:{RazorViewAttribute}({escapedPath}, typeof({generatedTypeName}))]";
+ }
+ else if (documentNode.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind &&
+ PageDirective.TryGetPageDirective(documentNode, out var pageDirective))
+ {
+ var escapedRoutePrefix = MakeVerbatimStringLiteral(pageDirective.RouteTemplate);
+ attribute = $"[assembly:{RazorPageAttribute}({escapedPath}, typeof({generatedTypeName}), {escapedRoutePrefix})]";
+ }
+ else
+ {
+ return;
+ }
+
+ var index = documentNode.Children.IndexOf(@namespace);
+ Debug.Assert(index >= 0);
+
+ var pageAttribute = new CSharpCodeIntermediateNode();
+ pageAttribute.Children.Add(new IntermediateToken()
+ {
+ Kind = TokenKind.CSharp,
+ Content = attribute,
+ });
+
+ documentNode.Children.Insert(index, pageAttribute);
+ }
+
+ private static string MakeVerbatimStringLiteral(string value)
+ {
+ if (value == null)
+ {
+ return "null";
+ }
+
+ value = value.Replace("\"", "\"\"");
+ return $"@\"{value}\"";
+ }
+
+ private static string ConvertToViewEnginePath(string relativePath)
+ {
+ if (string.IsNullOrEmpty(relativePath))
+ {
+ return null;
+ }
+
+ // Checking for both / and \ because a \ will become a /.
+ if (!relativePath.StartsWith("/") && !relativePath.StartsWith("\\"))
+ {
+ relativePath = "/" + relativePath;
+ }
+
+ relativePath = relativePath.Replace('\\', '/');
+ return relativePath;
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/CSharpIdentifier.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/CSharpIdentifier.cs
similarity index 97%
rename from src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/CSharpIdentifier.cs
rename to src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/CSharpIdentifier.cs
index e1ca2e343e..77a5e8f54e 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/CSharpIdentifier.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/CSharpIdentifier.cs
@@ -5,7 +5,7 @@ using System;
using System.Globalization;
using System.Text;
-namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
{
internal static class CSharpIdentifier
{
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ExtensionInitializer.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ExtensionInitializer.cs
new file mode 100644
index 0000000000..03d05f7f9b
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ExtensionInitializer.cs
@@ -0,0 +1,15 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ internal class ExtensionInitializer : RazorExtensionInitializer
+ {
+ public override void Initialize(RazorProjectEngineBuilder builder)
+ {
+ RazorExtensions.Register(builder);
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/IInjectTargetExtension.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/IInjectTargetExtension.cs
new file mode 100644
index 0000000000..c334e23c2b
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/IInjectTargetExtension.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public interface IInjectTargetExtension : ICodeTargetExtension
+ {
+ void WriteInjectProperty(CodeRenderingContext context, InjectIntermediateNode node);
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/IViewComponentTagHelperTargetExtension.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/IViewComponentTagHelperTargetExtension.cs
new file mode 100644
index 0000000000..f3e07a2b75
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/IViewComponentTagHelperTargetExtension.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public interface IViewComponentTagHelperTargetExtension : ICodeTargetExtension
+ {
+ void WriteViewComponentTagHelper(CodeRenderingContext context, ViewComponentTagHelperIntermediateNode node);
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectDirective.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectDirective.cs
new file mode 100644
index 0000000000..9442aaf641
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectDirective.cs
@@ -0,0 +1,124 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public static class InjectDirective
+ {
+ public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
+ "inject",
+ DirectiveKind.SingleLine,
+ builder =>
+ {
+ builder
+ .AddTypeToken(Resources.InjectDirective_TypeToken_Name, Resources.InjectDirective_TypeToken_Description)
+ .AddMemberToken(Resources.InjectDirective_MemberToken_Name, Resources.InjectDirective_MemberToken_Description);
+
+ builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
+ builder.Description = Resources.InjectDirective_Description;
+ });
+
+ public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddDirective(Directive);
+ builder.Features.Add(new Pass());
+ builder.AddTargetExtension(new InjectTargetExtension());
+ return builder;
+ }
+
+ internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
+ {
+ // Runs after the @model and @namespace directives
+ public override int Order => 10;
+
+ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ var visitor = new Visitor();
+ visitor.Visit(documentNode);
+ var modelType = ModelDirective.GetModelType(documentNode);
+
+ var properties = new HashSet(StringComparer.Ordinal);
+
+ 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;
+ }
+
+ typeName = typeName.Replace("", "<" + modelType + ">");
+
+ var injectNode = new InjectIntermediateNode()
+ {
+ TypeName = typeName,
+ MemberName = memberName,
+ };
+
+ visitor.Class.Children.Add(injectNode);
+ }
+ }
+ }
+
+ private class Visitor : IntermediateNodeWalker
+ {
+ public ClassDeclarationIntermediateNode Class { get; private set; }
+
+ public IList Directives { get; } = new List();
+
+ public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
+ {
+ if (Class == null)
+ {
+ Class = node;
+ }
+
+ base.VisitClassDeclaration(node);
+ }
+
+ public override void VisitDirective(DirectiveIntermediateNode node)
+ {
+ if (node.Directive == Directive)
+ {
+ Directives.Add(node);
+ }
+ }
+ }
+
+ #region Obsolete
+ [Obsolete("This method is obsolete and will be removed in a future version.")]
+ public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddDirective(Directive);
+ builder.Features.Add(new Pass());
+ builder.AddTargetExtension(new InjectTargetExtension());
+ return builder;
+ }
+ #endregion
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectIntermediateNode.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectIntermediateNode.cs
new file mode 100644
index 0000000000..4e3603f40d
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectIntermediateNode.cs
@@ -0,0 +1,58 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class InjectIntermediateNode : ExtensionIntermediateNode
+ {
+ public string TypeName { get; set; }
+
+ public string MemberName { get; set; }
+
+ public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
+
+ public override void Accept(IntermediateNodeVisitor visitor)
+ {
+ if (visitor == null)
+ {
+ throw new ArgumentNullException(nameof(visitor));
+ }
+
+ AcceptExtensionNode(this, visitor);
+ }
+
+ public override void WriteNode(CodeTarget target, CodeRenderingContext context)
+ {
+ if (target == null)
+ {
+ throw new ArgumentNullException(nameof(target));
+ }
+
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var extension = target.GetExtension();
+ if (extension == null)
+ {
+ ReportMissingCodeTargetExtension(context);
+ return;
+ }
+
+ extension.WriteInjectProperty(context, this);
+ }
+
+ public override void FormatNode(IntermediateNodeFormatter formatter)
+ {
+ formatter.WriteContent(MemberName);
+
+ formatter.WriteProperty(nameof(MemberName), MemberName);
+ formatter.WriteProperty(nameof(TypeName), TypeName);
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectTargetExtension.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectTargetExtension.cs
new file mode 100644
index 0000000000..1660b84d6d
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InjectTargetExtension.cs
@@ -0,0 +1,44 @@
+// 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.CodeGeneration;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class InjectTargetExtension : IInjectTargetExtension
+ {
+ private const string RazorInjectAttribute = "[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]";
+
+ public void WriteInjectProperty(CodeRenderingContext context, InjectIntermediateNode node)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (node == null)
+ {
+ throw new ArgumentNullException(nameof(node));
+ }
+
+ var property = $"public {node.TypeName} {node.MemberName} {{ get; private set; }}";
+
+ if (node.Source.HasValue)
+ {
+ using (context.CodeWriter.BuildLinePragma(node.Source.Value))
+ {
+ context.CodeWriter
+ .WriteLine(RazorInjectAttribute)
+ .WriteLine(property);
+ }
+ }
+ else
+ {
+ context.CodeWriter
+ .WriteLine(RazorInjectAttribute)
+ .WriteLine(property);
+ }
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InstrumentationPass.cs
similarity index 98%
rename from src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs
rename to src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InstrumentationPass.cs
index d80c2b9b68..f82ed32e45 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/InstrumentationPass.cs
@@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
-namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
{
public class InstrumentationPass : IntermediateNodePassBase, IRazorOptimizationPass
{
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj
new file mode 100644
index 0000000000..e77d4c3837
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj
@@ -0,0 +1,20 @@
+
+
+
+ ASP.NET Core design time hosting infrastructure for the Razor view engine.
+ netstandard2.0
+ $(PackageTags);aspnetcoremvc
+
+
+
+
+ Shared\CodeWriterExtensions.cs
+
+
+
+
+
+
+
+
+
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ModelDirective.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ModelDirective.cs
new file mode 100644
index 0000000000..cf80df5f68
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ModelDirective.cs
@@ -0,0 +1,152 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public static class ModelDirective
+ {
+ public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
+ "model",
+ DirectiveKind.SingleLine,
+ builder =>
+ {
+ builder.AddTypeToken(Resources.ModelDirective_TypeToken_Name, Resources.ModelDirective_TypeToken_Description);
+ builder.Usage = DirectiveUsage.FileScopedSinglyOccurring;
+ builder.Description = Resources.ModelDirective_Description;
+ });
+
+ public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddDirective(Directive);
+ builder.Features.Add(new Pass());
+ return builder;
+ }
+
+ public static string GetModelType(DocumentIntermediateNode document)
+ {
+ if (document == null)
+ {
+ throw new ArgumentNullException(nameof(document));
+ }
+
+ var visitor = new Visitor();
+ return GetModelType(document, visitor);
+ }
+
+ private static string GetModelType(DocumentIntermediateNode document, Visitor visitor)
+ {
+ visitor.Visit(document);
+
+ for (var i = visitor.ModelDirectives.Count - 1; i >= 0; i--)
+ {
+ var directive = visitor.ModelDirectives[i];
+
+ var tokens = directive.Tokens.ToArray();
+ if (tokens.Length >= 1)
+ {
+ return tokens[0].Content;
+ }
+ }
+
+ if (document.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind)
+ {
+ return visitor.Class.ClassName;
+ }
+ else
+ {
+ return "dynamic";
+ }
+ }
+
+ internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
+ {
+ // Runs after the @inherits directive
+ public override int Order => 5;
+
+ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ var visitor = new Visitor();
+ var modelType = GetModelType(documentNode, visitor);
+
+ if (documentNode.Options.DesignTime)
+ {
+ // Alias the TModel token to a known type.
+ // This allows design time compilation to succeed for Razor files where the token isn't replaced.
+ var typeName = $"global::{typeof(object).FullName}";
+ var usingNode = new UsingDirectiveIntermediateNode()
+ {
+ Content = $"TModel = {typeName}"
+ };
+
+ visitor.Namespace?.Children.Insert(0, usingNode);
+ }
+
+ var baseType = visitor.Class?.BaseType?.Replace("", "<" + modelType + ">");
+ visitor.Class.BaseType = baseType;
+ }
+ }
+
+ private class Visitor : IntermediateNodeWalker
+ {
+ public NamespaceDeclarationIntermediateNode Namespace { get; private set; }
+
+ public ClassDeclarationIntermediateNode Class { get; private set; }
+
+ public IList ModelDirectives { get; } = new List();
+
+ public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
+ {
+ if (Namespace == null)
+ {
+ Namespace = node;
+ }
+
+ base.VisitNamespaceDeclaration(node);
+ }
+
+ public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
+ {
+ if (Class == null)
+ {
+ Class = node;
+ }
+
+ base.VisitClassDeclaration(node);
+ }
+
+ public override void VisitDirective(DirectiveIntermediateNode node)
+ {
+ if (node.Directive == Directive)
+ {
+ ModelDirectives.Add(node);
+ }
+ }
+ }
+
+ #region Obsolete
+ [Obsolete("This method is obsolete and will be removed in a future version.")]
+ public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddDirective(Directive);
+ builder.Features.Add(new Pass());
+ return builder;
+ }
+ #endregion
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ModelExpressionPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ModelExpressionPass.cs
new file mode 100644
index 0000000000..ef8e8fb181
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ModelExpressionPass.cs
@@ -0,0 +1,85 @@
+// 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;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class ModelExpressionPass : IntermediateNodePassBase, IRazorOptimizationPass
+ {
+ private const string ModelExpressionTypeName = "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression";
+
+ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ var visitor = new Visitor();
+ visitor.Visit(documentNode);
+ }
+
+ private class Visitor : IntermediateNodeWalker
+ {
+ public List TagHelpers { get; } = new List();
+
+ public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
+ {
+ if (string.Equals(node.BoundAttribute.TypeName, ModelExpressionTypeName, StringComparison.Ordinal) ||
+ (node.IsIndexerNameMatch &&
+ string.Equals(node.BoundAttribute.IndexerTypeName, ModelExpressionTypeName, StringComparison.Ordinal)))
+ {
+ var expression = new CSharpExpressionIntermediateNode();
+
+ expression.Children.Add(new IntermediateToken()
+ {
+ Kind = TokenKind.CSharp,
+ Content = "ModelExpressionProvider.CreateModelExpression(ViewData, __model => ",
+ });
+
+ if (node.Children.Count == 1 && node.Children[0] is IntermediateToken token && token.IsCSharp)
+ {
+ // A 'simple' expression will look like __model => __model.Foo
+
+ expression.Children.Add(new IntermediateToken()
+ {
+ Kind = TokenKind.CSharp,
+ Content = "__model."
+ });
+
+ expression.Children.Add(token);
+ }
+ else
+ {
+ for (var i = 0; i < node.Children.Count; i++)
+ {
+ if (node.Children[i] is CSharpExpressionIntermediateNode nestedExpression)
+ {
+ for (var j = 0; j < nestedExpression.Children.Count; j++)
+ {
+ if (nestedExpression.Children[j] is IntermediateToken cSharpToken &&
+ cSharpToken.IsCSharp)
+ {
+ expression.Children.Add(cSharpToken);
+ }
+ }
+
+ continue;
+ }
+ }
+ }
+
+ expression.Children.Add(new IntermediateToken()
+ {
+ Kind = TokenKind.CSharp,
+ Content = ")",
+ });
+
+ node.Children.Clear();
+
+ node.Children.Add(expression);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/MvcImportProjectFeature.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/MvcImportProjectFeature.cs
new file mode 100644
index 0000000000..c25cb0d063
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/MvcImportProjectFeature.cs
@@ -0,0 +1,91 @@
+// 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.Razor.Language;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ internal class MvcImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature
+ {
+ private const string ImportsFileName = "_ViewImports.cshtml";
+
+ public IReadOnlyList GetImports(RazorProjectItem projectItem)
+ {
+ if (projectItem == null)
+ {
+ throw new ArgumentNullException(nameof(projectItem));
+ }
+
+ var imports = new List();
+ AddDefaultDirectivesImport(imports);
+
+ // We add hierarchical imports second so any default directive imports can be overridden.
+ AddHierarchicalImports(projectItem, imports);
+
+ return imports;
+ }
+
+ // Internal for testing
+ internal static void AddDefaultDirectivesImport(List imports)
+ {
+ imports.Add(DefaultDirectivesProjectItem.Instance);
+ }
+
+ // Internal for testing
+ internal void AddHierarchicalImports(RazorProjectItem projectItem, List imports)
+ {
+ // We want items in descending order. FindHierarchicalItems returns items in ascending order.
+ var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, ImportsFileName).Reverse();
+ imports.AddRange(importProjectItems);
+ }
+
+ private class DefaultDirectivesProjectItem : RazorProjectItem
+ {
+ private readonly byte[] _defaultImportBytes;
+
+ private DefaultDirectivesProjectItem()
+ {
+ var preamble = Encoding.UTF8.GetPreamble();
+ var content = @"
+@using System
+@using System.Collections.Generic
+@using System.Linq
+@using System.Threading.Tasks
+@using Microsoft.AspNetCore.Mvc
+@using Microsoft.AspNetCore.Mvc.Rendering
+@using Microsoft.AspNetCore.Mvc.ViewFeatures
+@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html
+@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json
+@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component
+@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url
+@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider
+@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor
+@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor
+@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor
+";
+ var contentBytes = Encoding.UTF8.GetBytes(content);
+
+ _defaultImportBytes = new byte[preamble.Length + contentBytes.Length];
+ preamble.CopyTo(_defaultImportBytes, 0);
+ contentBytes.CopyTo(_defaultImportBytes, preamble.Length);
+ }
+
+ public override string BasePath => null;
+
+ public override string FilePath => null;
+
+ public override string PhysicalPath => null;
+
+ public override bool Exists => true;
+
+ public static DefaultDirectivesProjectItem Instance { get; } = new DefaultDirectivesProjectItem();
+
+ public override Stream Read() => new MemoryStream(_defaultImportBytes);
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/MvcViewDocumentClassifierPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/MvcViewDocumentClassifierPass.cs
new file mode 100644
index 0000000000..7ec6a1360e
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/MvcViewDocumentClassifierPass.cs
@@ -0,0 +1,71 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class MvcViewDocumentClassifierPass : DocumentClassifierPassBase
+ {
+ public static readonly string MvcViewDocumentKind = "mvc.1.0.view";
+
+ protected override string DocumentKind => MvcViewDocumentKind;
+
+ protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) => true;
+
+ protected override void OnDocumentStructureCreated(
+ RazorCodeDocument codeDocument,
+ NamespaceDeclarationIntermediateNode @namespace,
+ ClassDeclarationIntermediateNode @class,
+ MethodDeclarationIntermediateNode method)
+ {
+ base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
+
+ @namespace.Content = "AspNetCore";
+
+ var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
+ if (string.IsNullOrEmpty(filePath))
+ {
+ // It's possible for a Razor document to not have a file path.
+ // Eg. When we try to generate code for an in memory document like default imports.
+ var checksum = BytesToString(codeDocument.Source.GetChecksum());
+ @class.ClassName = $"AspNetCore_{checksum}";
+ }
+ else
+ {
+ @class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath);
+ }
+
+ @class.BaseType = "global::Microsoft.AspNetCore.Mvc.Razor.RazorPage";
+ @class.Modifiers.Clear();
+ @class.Modifiers.Add("public");
+
+ method.MethodName = "ExecuteAsync";
+ method.Modifiers.Clear();
+ method.Modifiers.Add("public");
+ method.Modifiers.Add("async");
+ method.Modifiers.Add("override");
+ method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}";
+ }
+
+ private static string BytesToString(byte[] bytes)
+ {
+ if (bytes == null)
+ {
+ throw new ArgumentNullException(nameof(bytes));
+ }
+
+ var result = new StringBuilder(bytes.Length);
+ for (var i = 0; i < bytes.Length; i++)
+ {
+ // The x2 format means lowercase hex, where each byte is a 2-character string.
+ result.Append(bytes[i].ToString("x2"));
+ }
+
+ return result.ToString();
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/NamespaceDirective.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/NamespaceDirective.cs
new file mode 100644
index 0000000000..de05d354b1
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/NamespaceDirective.cs
@@ -0,0 +1,204 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public static class NamespaceDirective
+ {
+ private static readonly char[] Separators = new char[] { '\\', '/' };
+
+ public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
+ "namespace",
+ DirectiveKind.SingleLine,
+ builder =>
+ {
+ builder.AddNamespaceToken(
+ Resources.NamespaceDirective_NamespaceToken_Name,
+ Resources.NamespaceDirective_NamespaceToken_Description);
+ builder.Usage = DirectiveUsage.FileScopedSinglyOccurring;
+ builder.Description = Resources.NamespaceDirective_Description;
+ });
+
+ public static void Register(RazorProjectEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ builder.AddDirective(Directive);
+ builder.Features.Add(new Pass());
+ }
+
+ // internal for testing
+ internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
+ {
+ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind &&
+ documentNode.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind)
+ {
+ // Not a page. Skip.
+ return;
+ }
+
+ var visitor = new Visitor();
+ visitor.Visit(documentNode);
+
+ var directive = visitor.LastNamespaceDirective;
+ if (directive == null)
+ {
+ // No namespace set. Skip.
+ return;
+ }
+
+ var @namespace = visitor.FirstNamespace;
+ if (@namespace == null)
+ {
+ // No namespace node. Skip.
+ return;
+ }
+
+ @namespace.Content = GetNamespace(codeDocument.Source.FilePath, directive);
+ }
+ }
+
+ // internal for testing.
+ //
+ // This code does a best-effort attempt to compute a namespace 'suffix' - the path difference between
+ // where the @namespace directive appears and where the current document is on disk.
+ //
+ // In the event that these two source either don't have FileNames set or don't follow a coherent hierarchy,
+ // we will just use the namespace verbatim.
+ internal static string GetNamespace(string source, DirectiveIntermediateNode directive)
+ {
+ var directiveSource = NormalizeDirectory(directive.Source?.FilePath);
+
+ var baseNamespace = directive.Tokens.FirstOrDefault()?.Content;
+ if (string.IsNullOrEmpty(baseNamespace))
+ {
+ // The namespace directive was incomplete.
+ return string.Empty;
+ }
+
+ if (string.IsNullOrEmpty(source) || directiveSource == null)
+ {
+ // No sources, can't compute a suffix.
+ return baseNamespace;
+ }
+
+ // We're specifically using OrdinalIgnoreCase here because Razor treats all paths as case-insensitive.
+ if (!source.StartsWith(directiveSource, StringComparison.OrdinalIgnoreCase) ||
+ source.Length <= directiveSource.Length)
+ {
+ // The imports are not from the directory hierarchy, can't compute a suffix.
+ return baseNamespace;
+ }
+
+ // OK so that this point we know that the 'imports' file containing this directive is in the directory
+ // hierarchy of this soure file. This is the case where we can append a suffix to the baseNamespace.
+ //
+ // Everything so far has just been defensiveness on our part.
+
+ var builder = new StringBuilder(baseNamespace);
+
+ var segments = source.Substring(directiveSource.Length).Split(Separators);
+
+ // 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]));
+ }
+
+ return builder.ToString();
+ }
+
+ // We want to normalize the path of the file containing the '@namespace' directive to just the containing
+ // directory with a trailing separator.
+ //
+ // Not using Path.GetDirectoryName here because it doesn't meet these requirements, and we want to handle
+ // both 'view engine' style paths and absolute paths.
+ //
+ // We also don't normalize the separators here. We expect that all documents are using a consistent style of path.
+ //
+ // If we can't normalize the path, we just return null so it will be ignored.
+ private static string NormalizeDirectory(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return null;
+ }
+
+ var lastSeparator = path.LastIndexOfAny(Separators);
+ if (lastSeparator == -1)
+ {
+ return null;
+ }
+
+ // Includes the separator
+ return path.Substring(0, lastSeparator + 1);
+ }
+
+ private class Visitor : IntermediateNodeWalker
+ {
+ public ClassDeclarationIntermediateNode FirstClass { get; private set; }
+
+ public NamespaceDeclarationIntermediateNode FirstNamespace { get; private set; }
+
+ // We want the last one, so get them all and then .
+ public DirectiveIntermediateNode LastNamespaceDirective { get; private set; }
+
+ public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
+ {
+ if (FirstNamespace == null)
+ {
+ FirstNamespace = node;
+ }
+
+ base.VisitNamespaceDeclaration(node);
+ }
+
+ public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
+ {
+ if (FirstClass == null)
+ {
+ FirstClass = node;
+ }
+
+ base.VisitClassDeclaration(node);
+ }
+
+ public override void VisitDirective(DirectiveIntermediateNode node)
+ {
+ if (node.Directive == Directive)
+ {
+ LastNamespaceDirective = node;
+ }
+
+ base.VisitDirective(node);
+ }
+ }
+
+ #region Obsolete
+ [Obsolete("This method is obsolete and will be removed in a future version.")]
+ public static void Register(IRazorEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ builder.AddDirective(Directive);
+ builder.Features.Add(new Pass());
+ }
+ #endregion
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/PageDirective.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/PageDirective.cs
new file mode 100644
index 0000000000..9b483f9b9f
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/PageDirective.cs
@@ -0,0 +1,121 @@
+// 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 Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class PageDirective
+ {
+ public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
+ "page",
+ DirectiveKind.SingleLine,
+ builder =>
+ {
+ builder.AddOptionalStringToken(Resources.PageDirective_RouteToken_Name, Resources.PageDirective_RouteToken_Description);
+ builder.Usage = DirectiveUsage.FileScopedSinglyOccurring;
+ 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);
+ return builder;
+ }
+
+ public static bool TryGetPageDirective(DocumentIntermediateNode documentNode, out PageDirective pageDirective)
+ {
+ var visitor = new Visitor();
+ for (var i = 0; i < documentNode.Children.Count; i++)
+ {
+ visitor.Visit(documentNode.Children[i]);
+ }
+
+ if (visitor.DirectiveTokens == null)
+ {
+ pageDirective = null;
+ return false;
+ }
+
+ var tokens = visitor.DirectiveTokens.ToList();
+ string routeTemplate = null;
+ if (tokens.Count > 0)
+ {
+ routeTemplate = TrimQuotes(tokens[0].Content);
+ }
+
+ pageDirective = new PageDirective(routeTemplate, visitor.DirectiveNode);
+ return true;
+ }
+
+ private static string TrimQuotes(string content)
+ {
+ // Tokens aren't captured if they're malformed. Therefore, this method will
+ // always be called with a valid token content.
+ Debug.Assert(content.Length >= 2);
+ Debug.Assert(content.StartsWith("\"", StringComparison.Ordinal));
+ Debug.Assert(content.EndsWith("\"", StringComparison.Ordinal));
+
+ return content.Substring(1, content.Length - 2);
+ }
+
+ private class Visitor : IntermediateNodeWalker
+ {
+ public IntermediateNode DirectiveNode { get; private set; }
+
+ public IEnumerable DirectiveTokens { get; private set; }
+
+ public override void VisitDirective(DirectiveIntermediateNode node)
+ {
+ if (node.Directive == Directive)
+ {
+ DirectiveNode = node;
+ DirectiveTokens = node.Tokens;
+ }
+ }
+
+ public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
+ {
+ if (DirectiveTokens == null && node.Directive == Directive)
+ {
+ DirectiveNode = node;
+ DirectiveTokens = node.Tokens;
+ }
+ }
+ }
+
+ #region Obsolete
+ [Obsolete("This method is obsolete and will be removed in a future version.")]
+ public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddDirective(Directive);
+ return builder;
+ }
+ #endregion
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/PagesPropertyInjectionPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/PagesPropertyInjectionPass.cs
new file mode 100644
index 0000000000..4010c1903b
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/PagesPropertyInjectionPass.cs
@@ -0,0 +1,57 @@
+// 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.Mvc.Razor.Extensions.Version2_X
+{
+ public class PagesPropertyInjectionPass : IntermediateNodePassBase, IRazorOptimizationPass
+ {
+ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind)
+ {
+ return;
+ }
+
+ var modelType = ModelDirective.GetModelType(documentNode);
+ var visitor = new Visitor();
+ visitor.Visit(documentNode);
+
+ var @class = visitor.Class;
+
+ var viewDataType = $"global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<{modelType}>";
+ var vddProperty = new CSharpCodeIntermediateNode();
+ vddProperty.Children.Add(new IntermediateToken()
+ {
+ Kind = TokenKind.CSharp,
+ Content = $"public {viewDataType} ViewData => ({viewDataType})PageContext?.ViewData;",
+ });
+ @class.Children.Add(vddProperty);
+
+ var modelProperty = new CSharpCodeIntermediateNode();
+ modelProperty.Children.Add(new IntermediateToken()
+ {
+ Kind = TokenKind.CSharp,
+ Content = $"public {modelType} Model => ViewData.Model;",
+ });
+ @class.Children.Add(modelProperty);
+ }
+
+ private class Visitor : IntermediateNodeWalker
+ {
+ public ClassDeclarationIntermediateNode Class { get; private set; }
+
+ public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
+ {
+ if (Class == null)
+ {
+ Class = node;
+ }
+
+ base.VisitClassDeclaration(node);
+ }
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/AssemblyInfo.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..a7a402e09a
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/AssemblyInfo.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+using Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X;
+using Microsoft.AspNetCore.Razor.Language;
+
+[assembly: ProvideRazorExtensionInitializer("MVC-2.0", typeof(ExtensionInitializer))]
+[assembly: ProvideRazorExtensionInitializer("MVC-2.1", typeof(ExtensionInitializer))]
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/Resources.Designer.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..ec4883803c
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/Resources.Designer.cs
@@ -0,0 +1,338 @@
+//
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// Value cannot be null or empty.
+ ///
+ internal static string ArgumentCannotBeNullOrEmpty
+ {
+ get => GetString("ArgumentCannotBeNullOrEmpty");
+ }
+
+ ///
+ /// Value cannot be null or empty.
+ ///
+ internal static string FormatArgumentCannotBeNullOrEmpty()
+ => GetString("ArgumentCannotBeNullOrEmpty");
+
+ ///
+ /// Inject a service from the application's service container into a property.
+ ///
+ internal static string InjectDirective_Description
+ {
+ get => GetString("InjectDirective_Description");
+ }
+
+ ///
+ /// Inject a service from the application's service container into a property.
+ ///
+ internal static string FormatInjectDirective_Description()
+ => GetString("InjectDirective_Description");
+
+ ///
+ /// The name of the property.
+ ///
+ internal static string InjectDirective_MemberToken_Description
+ {
+ get => GetString("InjectDirective_MemberToken_Description");
+ }
+
+ ///
+ /// The name of the property.
+ ///
+ internal static string FormatInjectDirective_MemberToken_Description()
+ => GetString("InjectDirective_MemberToken_Description");
+
+ ///
+ /// PropertyName
+ ///
+ internal static string InjectDirective_MemberToken_Name
+ {
+ get => GetString("InjectDirective_MemberToken_Name");
+ }
+
+ ///
+ /// PropertyName
+ ///
+ internal static string FormatInjectDirective_MemberToken_Name()
+ => GetString("InjectDirective_MemberToken_Name");
+
+ ///
+ /// The type of the service to inject.
+ ///
+ internal static string InjectDirective_TypeToken_Description
+ {
+ get => GetString("InjectDirective_TypeToken_Description");
+ }
+
+ ///
+ /// The type of the service to inject.
+ ///
+ internal static string FormatInjectDirective_TypeToken_Description()
+ => GetString("InjectDirective_TypeToken_Description");
+
+ ///
+ /// TypeName
+ ///
+ internal static string InjectDirective_TypeToken_Name
+ {
+ get => GetString("InjectDirective_TypeToken_Name");
+ }
+
+ ///
+ /// TypeName
+ ///
+ internal static string FormatInjectDirective_TypeToken_Name()
+ => GetString("InjectDirective_TypeToken_Name");
+
+ ///
+ /// Specify the view or page model for the page.
+ ///
+ internal static string ModelDirective_Description
+ {
+ get => GetString("ModelDirective_Description");
+ }
+
+ ///
+ /// Specify the view or page model for the page.
+ ///
+ internal static string FormatModelDirective_Description()
+ => GetString("ModelDirective_Description");
+
+ ///
+ /// The model type.
+ ///
+ internal static string ModelDirective_TypeToken_Description
+ {
+ get => GetString("ModelDirective_TypeToken_Description");
+ }
+
+ ///
+ /// The model type.
+ ///
+ internal static string FormatModelDirective_TypeToken_Description()
+ => GetString("ModelDirective_TypeToken_Description");
+
+ ///
+ /// TypeName
+ ///
+ internal static string ModelDirective_TypeToken_Name
+ {
+ get => GetString("ModelDirective_TypeToken_Name");
+ }
+
+ ///
+ /// TypeName
+ ///
+ internal static string FormatModelDirective_TypeToken_Name()
+ => GetString("ModelDirective_TypeToken_Name");
+
+ ///
+ /// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
+ ///
+ internal static string MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword
+ {
+ get => GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword");
+ }
+
+ ///
+ /// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
+ ///
+ internal static string FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"), p0);
+
+ ///
+ /// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
+ ///
+ internal static string MvcRazorCodeParser_InjectDirectivePropertyNameRequired
+ {
+ get => GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired");
+ }
+
+ ///
+ /// A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
+ ///
+ internal static string FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_InjectDirectivePropertyNameRequired"), p0);
+
+ ///
+ /// The '{0}' keyword must be followed by a type name on the same line.
+ ///
+ internal static string MvcRazorCodeParser_KeywordMustBeFollowedByTypeName
+ {
+ get => GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName");
+ }
+
+ ///
+ /// The '{0}' keyword must be followed by a type name on the same line.
+ ///
+ internal static string FormatMvcRazorCodeParser_KeywordMustBeFollowedByTypeName(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_KeywordMustBeFollowedByTypeName"), p0);
+
+ ///
+ /// Only one '{0}' statement is allowed in a file.
+ ///
+ internal static string MvcRazorCodeParser_OnlyOneModelStatementIsAllowed
+ {
+ get => GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed");
+ }
+
+ ///
+ /// Only one '{0}' statement is allowed in a file.
+ ///
+ internal static string FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0);
+
+ ///
+ /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
+ ///
+ internal static string MvcRazorParser_InvalidPropertyType
+ {
+ get => GetString("MvcRazorParser_InvalidPropertyType");
+ }
+
+ ///
+ /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
+ ///
+ internal static string FormatMvcRazorParser_InvalidPropertyType(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorParser_InvalidPropertyType"), p0, p1, p2);
+
+ ///
+ /// Specify the base namespace for the page.
+ ///
+ internal static string NamespaceDirective_Description
+ {
+ get => GetString("NamespaceDirective_Description");
+ }
+
+ ///
+ /// Specify the base namespace for the page.
+ ///
+ internal static string FormatNamespaceDirective_Description()
+ => GetString("NamespaceDirective_Description");
+
+ ///
+ /// The namespace for the page.
+ ///
+ internal static string NamespaceDirective_NamespaceToken_Description
+ {
+ get => GetString("NamespaceDirective_NamespaceToken_Description");
+ }
+
+ ///
+ /// The namespace for the page.
+ ///
+ internal static string FormatNamespaceDirective_NamespaceToken_Description()
+ => GetString("NamespaceDirective_NamespaceToken_Description");
+
+ ///
+ /// Namespace
+ ///
+ internal static string NamespaceDirective_NamespaceToken_Name
+ {
+ get => GetString("NamespaceDirective_NamespaceToken_Name");
+ }
+
+ ///
+ /// Namespace
+ ///
+ internal static string FormatNamespaceDirective_NamespaceToken_Name()
+ => GetString("NamespaceDirective_NamespaceToken_Name");
+
+ ///
+ /// The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file.
+ ///
+ internal static string PageDirectiveCannotBeImported
+ {
+ get => GetString("PageDirectiveCannotBeImported");
+ }
+
+ ///
+ /// The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file.
+ ///
+ internal static string FormatPageDirectiveCannotBeImported(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PageDirectiveCannotBeImported"), p0, p1);
+
+ ///
+ /// The '@{0}' directive must exist at the top of the file. Only comments and whitespace are allowed before the '@{0}' directive.
+ ///
+ internal static string PageDirectiveMustExistAtTheTopOfFile
+ {
+ get => GetString("PageDirectiveMustExistAtTheTopOfFile");
+ }
+
+ ///
+ /// The '@{0}' directive must exist at the top of the file. Only comments and whitespace are allowed before the '@{0}' directive.
+ ///
+ internal static string FormatPageDirectiveMustExistAtTheTopOfFile(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("PageDirectiveMustExistAtTheTopOfFile"), p0);
+
+ ///
+ /// Mark the page as a Razor Page.
+ ///
+ internal static string PageDirective_Description
+ {
+ get => GetString("PageDirective_Description");
+ }
+
+ ///
+ /// Mark the page as a Razor Page.
+ ///
+ internal static string FormatPageDirective_Description()
+ => GetString("PageDirective_Description");
+
+ ///
+ /// An optional route template for the page.
+ ///
+ internal static string PageDirective_RouteToken_Description
+ {
+ get => GetString("PageDirective_RouteToken_Description");
+ }
+
+ ///
+ /// An optional route template for the page.
+ ///
+ internal static string FormatPageDirective_RouteToken_Description()
+ => GetString("PageDirective_RouteToken_Description");
+
+ ///
+ /// RouteTemplate
+ ///
+ internal static string PageDirective_RouteToken_Name
+ {
+ get => GetString("PageDirective_RouteToken_Name");
+ }
+
+ ///
+ /// RouteTemplate
+ ///
+ internal static string FormatPageDirective_RouteToken_Name()
+ => GetString("PageDirective_RouteToken_Name");
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/ViewComponentResources.Designer.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/ViewComponentResources.Designer.cs
new file mode 100644
index 0000000000..3309adf1c7
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Properties/ViewComponentResources.Designer.cs
@@ -0,0 +1,100 @@
+//
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class ViewComponentResources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentResources", typeof(ViewComponentResources).GetTypeInfo().Assembly);
+
+ ///
+ /// View component '{0}' must have exactly one public method named '{1}' or '{2}'.
+ ///
+ internal static string ViewComponent_AmbiguousMethods
+ {
+ get => GetString("ViewComponent_AmbiguousMethods");
+ }
+
+ ///
+ /// View component '{0}' must have exactly one public method named '{1}' or '{2}'.
+ ///
+ internal static string FormatViewComponent_AmbiguousMethods(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AmbiguousMethods"), p0, p1, p2);
+
+ ///
+ /// Method '{0}' of view component '{1}' should be declared to return {2}<T>.
+ ///
+ internal static string ViewComponent_AsyncMethod_ShouldReturnTask
+ {
+ get => GetString("ViewComponent_AsyncMethod_ShouldReturnTask");
+ }
+
+ ///
+ /// Method '{0}' of view component '{1}' should be declared to return {2}<T>.
+ ///
+ internal static string FormatViewComponent_AsyncMethod_ShouldReturnTask(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0, p1, p2);
+
+ ///
+ /// Could not find an '{0}' or '{1}' method for the view component '{2}'.
+ ///
+ internal static string ViewComponent_CannotFindMethod
+ {
+ get => GetString("ViewComponent_CannotFindMethod");
+ }
+
+ ///
+ /// Could not find an '{0}' or '{1}' method for the view component '{2}'.
+ ///
+ internal static string FormatViewComponent_CannotFindMethod(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0, p1, p2);
+
+ ///
+ /// Method '{0}' of view component '{1}' cannot return a {2}.
+ ///
+ internal static string ViewComponent_SyncMethod_CannotReturnTask
+ {
+ get => GetString("ViewComponent_SyncMethod_CannotReturnTask");
+ }
+
+ ///
+ /// Method '{0}' of view component '{1}' cannot return a {2}.
+ ///
+ internal static string FormatViewComponent_SyncMethod_CannotReturnTask(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_CannotReturnTask"), p0, p1, p2);
+
+ ///
+ /// Method '{0}' of view component '{1}' should be declared to return a value.
+ ///
+ internal static string ViewComponent_SyncMethod_ShouldReturnValue
+ {
+ get => GetString("ViewComponent_SyncMethod_ShouldReturnValue");
+ }
+
+ ///
+ /// Method '{0}' of view component '{1}' should be declared to return a value.
+ ///
+ internal static string FormatViewComponent_SyncMethod_ShouldReturnValue(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0, p1);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorExtensions.cs
new file mode 100644
index 0000000000..69cf7e2055
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorExtensions.cs
@@ -0,0 +1,48 @@
+// 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.Extensions;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public static class RazorExtensions
+ {
+ public static void Register(RazorProjectEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ InjectDirective.Register(builder);
+ ModelDirective.Register(builder);
+ NamespaceDirective.Register(builder);
+ PageDirective.Register(builder);
+
+ InheritsDirective.Register(builder);
+ SectionDirective.Register(builder);
+
+ builder.Features.Add(new DefaultTagHelperDescriptorProvider());
+ builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
+
+ builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
+ builder.AddTargetExtension(new TemplateTargetExtension()
+ {
+ TemplateTypeName = "global::Microsoft.AspNetCore.Mvc.Razor.HelperResult",
+ });
+
+ builder.Features.Add(new ModelExpressionPass());
+ builder.Features.Add(new PagesPropertyInjectionPass());
+ builder.Features.Add(new ViewComponentTagHelperPass());
+ builder.Features.Add(new RazorPageDocumentClassifierPass());
+ builder.Features.Add(new MvcViewDocumentClassifierPass());
+ builder.Features.Add(new AssemblyAttributeInjectionPass());
+ builder.Features.Add(new InstrumentationPass());
+
+ builder.SetImportFeature(new MvcImportProjectFeature());
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorExtensionsDiagnosticFactory.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorExtensionsDiagnosticFactory.cs
new file mode 100644
index 0000000000..05a01d38a9
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorExtensionsDiagnosticFactory.cs
@@ -0,0 +1,131 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ internal class RazorExtensionsDiagnosticFactory
+ {
+ private const string DiagnosticPrefix = "RZ";
+
+ internal static readonly RazorDiagnosticDescriptor ViewComponent_CannotFindMethod =
+ new RazorDiagnosticDescriptor(
+ $"{DiagnosticPrefix}3900",
+ () => ViewComponentResources.ViewComponent_CannotFindMethod,
+ RazorDiagnosticSeverity.Error);
+
+ public static RazorDiagnostic CreateViewComponent_CannotFindMethod(string tagHelperType)
+ {
+ var diagnostic = RazorDiagnostic.Create(
+ ViewComponent_CannotFindMethod,
+ new SourceSpan(SourceLocation.Undefined, contentLength: 0),
+ ViewComponentTypes.SyncMethodName,
+ ViewComponentTypes.AsyncMethodName,
+ tagHelperType);
+
+ return diagnostic;
+ }
+
+ internal static readonly RazorDiagnosticDescriptor ViewComponent_AmbiguousMethods =
+ new RazorDiagnosticDescriptor(
+ $"{DiagnosticPrefix}3901",
+ () => ViewComponentResources.ViewComponent_AmbiguousMethods,
+ RazorDiagnosticSeverity.Error);
+
+ public static RazorDiagnostic CreateViewComponent_AmbiguousMethods(string tagHelperType)
+ {
+ var diagnostic = RazorDiagnostic.Create(
+ ViewComponent_AmbiguousMethods,
+ new SourceSpan(SourceLocation.Undefined, contentLength: 0),
+ tagHelperType,
+ ViewComponentTypes.SyncMethodName,
+ ViewComponentTypes.AsyncMethodName);
+
+ return diagnostic;
+ }
+
+ internal static readonly RazorDiagnosticDescriptor ViewComponent_AsyncMethod_ShouldReturnTask =
+ new RazorDiagnosticDescriptor(
+ $"{DiagnosticPrefix}3902",
+ () => ViewComponentResources.ViewComponent_AsyncMethod_ShouldReturnTask,
+ RazorDiagnosticSeverity.Error);
+
+ public static RazorDiagnostic CreateViewComponent_AsyncMethod_ShouldReturnTask(string tagHelperType)
+ {
+ var diagnostic = RazorDiagnostic.Create(
+ ViewComponent_AsyncMethod_ShouldReturnTask,
+ new SourceSpan(SourceLocation.Undefined, contentLength: 0),
+ ViewComponentTypes.AsyncMethodName,
+ tagHelperType,
+ nameof(Task));
+
+ return diagnostic;
+ }
+
+ internal static readonly RazorDiagnosticDescriptor ViewComponent_SyncMethod_ShouldReturnValue =
+ new RazorDiagnosticDescriptor(
+ $"{DiagnosticPrefix}3903",
+ () => ViewComponentResources.ViewComponent_SyncMethod_ShouldReturnValue,
+ RazorDiagnosticSeverity.Error);
+
+ public static RazorDiagnostic CreateViewComponent_SyncMethod_ShouldReturnValue(string tagHelperType)
+ {
+ var diagnostic = RazorDiagnostic.Create(
+ ViewComponent_SyncMethod_ShouldReturnValue,
+ new SourceSpan(SourceLocation.Undefined, contentLength: 0),
+ ViewComponentTypes.SyncMethodName,
+ tagHelperType);
+
+ return diagnostic;
+ }
+
+ internal static readonly RazorDiagnosticDescriptor ViewComponent_SyncMethod_CannotReturnTask =
+ new RazorDiagnosticDescriptor(
+ $"{DiagnosticPrefix}3904",
+ () => ViewComponentResources.ViewComponent_SyncMethod_CannotReturnTask,
+ RazorDiagnosticSeverity.Error);
+
+ public static RazorDiagnostic CreateViewComponent_SyncMethod_CannotReturnTask(string tagHelperType)
+ {
+ var diagnostic = RazorDiagnostic.Create(
+ ViewComponent_SyncMethod_CannotReturnTask,
+ new SourceSpan(SourceLocation.Undefined, contentLength: 0),
+ ViewComponentTypes.SyncMethodName,
+ tagHelperType,
+ nameof(Task));
+
+ return diagnostic;
+ }
+
+ internal static readonly RazorDiagnosticDescriptor PageDirective_CannotBeImported =
+ new RazorDiagnosticDescriptor(
+ $"{DiagnosticPrefix}3905",
+ () => 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;
+ }
+
+ internal static readonly RazorDiagnosticDescriptor PageDirective_MustExistAtTheTopOfFile =
+ new RazorDiagnosticDescriptor(
+ $"{DiagnosticPrefix}3906",
+ () => Resources.PageDirectiveMustExistAtTheTopOfFile,
+ RazorDiagnosticSeverity.Error);
+
+ public static RazorDiagnostic CreatePageDirective_MustExistAtTheTopOfFile(SourceSpan source)
+ {
+ var fileName = Path.GetFileName(source.FilePath);
+ var diagnostic = RazorDiagnostic.Create(PageDirective_MustExistAtTheTopOfFile, source, PageDirective.Directive.Directive);
+
+ return diagnostic;
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorPageDocumentClassifierPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorPageDocumentClassifierPass.cs
new file mode 100644
index 0000000000..a5de7d0783
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/RazorPageDocumentClassifierPass.cs
@@ -0,0 +1,164 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Text;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.Extensions;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class RazorPageDocumentClassifierPass : DocumentClassifierPassBase
+ {
+ public static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page";
+ public static readonly string RouteTemplateKey = "RouteTemplate";
+
+ private static readonly RazorProjectEngine LeadingDirectiveParsingEngine = RazorProjectEngine.Create(
+ RazorConfiguration.Default,
+ RazorProjectFileSystem.Create("/"),
+ builder =>
+ {
+ for (var i = builder.Phases.Count - 1; i >= 0; i--)
+ {
+ var phase = builder.Phases[i];
+ builder.Phases.RemoveAt(i);
+ if (phase is IRazorDocumentClassifierPhase)
+ {
+ break;
+ }
+ }
+
+ RazorExtensions.Register(builder);
+ builder.Features.Add(new LeadingDirectiveParserOptionsFeature());
+ });
+
+ protected override string DocumentKind => RazorPageDocumentKind;
+
+ protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ return PageDirective.TryGetPageDirective(documentNode, out var pageDirective);
+ }
+
+ protected override void OnDocumentStructureCreated(
+ RazorCodeDocument codeDocument,
+ NamespaceDeclarationIntermediateNode @namespace,
+ ClassDeclarationIntermediateNode @class,
+ MethodDeclarationIntermediateNode method)
+ {
+ base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
+
+ @namespace.Content = "AspNetCore";
+
+ @class.BaseType = "global::Microsoft.AspNetCore.Mvc.RazorPages.Page";
+
+ var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
+ if (string.IsNullOrEmpty(filePath))
+ {
+ // It's possible for a Razor document to not have a file path.
+ // Eg. When we try to generate code for an in memory document like default imports.
+ var checksum = BytesToString(codeDocument.Source.GetChecksum());
+ @class.ClassName = $"AspNetCore_{checksum}";
+ }
+ else
+ {
+ @class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath);
+ }
+
+ @class.Modifiers.Clear();
+ @class.Modifiers.Add("public");
+
+ method.MethodName = "ExecuteAsync";
+ method.Modifiers.Clear();
+ method.Modifiers.Add("public");
+ method.Modifiers.Add("async");
+ method.Modifiers.Add("override");
+ method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}";
+
+ var document = codeDocument.GetDocumentIntermediateNode();
+ PageDirective.TryGetPageDirective(document, out var pageDirective);
+
+ EnsureValidPageDirective(codeDocument, pageDirective);
+
+ AddRouteTemplateMetadataAttribute(@namespace, @class, pageDirective);
+ }
+
+ private static void AddRouteTemplateMetadataAttribute(NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, PageDirective pageDirective)
+ {
+ if (string.IsNullOrEmpty(pageDirective.RouteTemplate))
+ {
+ return;
+ }
+
+ var classIndex = @namespace.Children.IndexOf(@class);
+ if (classIndex == -1)
+ {
+ return;
+ }
+
+ var metadataAttributeNode = new RazorCompiledItemMetadataAttributeIntermediateNode
+ {
+ Key = RouteTemplateKey,
+ Value = pageDirective.RouteTemplate,
+ };
+ // Metadata attributes need to be inserted right before the class declaration.
+ @namespace.Children.Insert(classIndex, metadataAttributeNode);
+ }
+
+ private void EnsureValidPageDirective(RazorCodeDocument codeDocument, PageDirective pageDirective)
+ {
+ Debug.Assert(pageDirective != null);
+
+ if (pageDirective.DirectiveNode.IsImported())
+ {
+ pageDirective.DirectiveNode.Diagnostics.Add(
+ RazorExtensionsDiagnosticFactory.CreatePageDirective_CannotBeImported(pageDirective.DirectiveNode.Source.Value));
+ }
+ else
+ {
+ // The document contains a page directive and it is not imported.
+ // We now want to make sure this page directive exists at the top of the file.
+ // We are going to do that by re-parsing the document until the very first line that is not Razor comment
+ // or whitespace. We then make sure the page directive still exists in the re-parsed IR tree.
+ var leadingDirectiveCodeDocument = RazorCodeDocument.Create(codeDocument.Source);
+ LeadingDirectiveParsingEngine.Engine.Process(leadingDirectiveCodeDocument);
+
+ var leadingDirectiveDocumentNode = leadingDirectiveCodeDocument.GetDocumentIntermediateNode();
+ if (!PageDirective.TryGetPageDirective(leadingDirectiveDocumentNode, out var _))
+ {
+ // The page directive is not the leading directive. Add an error.
+ pageDirective.DirectiveNode.Diagnostics.Add(
+ RazorExtensionsDiagnosticFactory.CreatePageDirective_MustExistAtTheTopOfFile(pageDirective.DirectiveNode.Source.Value));
+ }
+ }
+ }
+
+ private class LeadingDirectiveParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorParserOptionsFeature
+ {
+ public int Order { get; }
+
+ public void Configure(RazorParserOptionsBuilder options)
+ {
+ options.ParseLeadingDirectives = true;
+ }
+ }
+
+ private static string BytesToString(byte[] bytes)
+ {
+ if (bytes == null)
+ {
+ throw new ArgumentNullException(nameof(bytes));
+ }
+
+ var result = new StringBuilder(bytes.Length);
+ for (var i = 0; i < bytes.Length; i++)
+ {
+ // The x2 format means lowercase hex, where each byte is a 2-character string.
+ result.Append(bytes[i].ToString("x2"));
+ }
+
+ return result.ToString();
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Resources.resx b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Resources.resx
new file mode 100644
index 0000000000..474537197e
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/Resources.resx
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Value cannot be null or empty.
+
+
+ Inject a service from the application's service container into a property.
+
+
+ The name of the property.
+
+
+ PropertyName
+
+
+ The type of the service to inject.
+
+
+ TypeName
+
+
+ Specify the view or page model for the page.
+
+
+ The model type.
+
+
+ TypeName
+
+
+ The 'inherits' keyword is not allowed when a '{0}' keyword is used.
+
+
+ A property name must be specified when using the '{0}' statement. Format for a '{0}' statement is '@{0} <Type Name> <Property Name>'.
+
+
+ The '{0}' keyword must be followed by a type name on the same line.
+
+
+ Only one '{0}' statement is allowed in a file.
+
+
+ Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.
+
+
+ Specify the base namespace for the page.
+
+
+ The namespace for the page.
+
+
+ Namespace
+
+
+ The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file.
+
+
+ The '@{0}' directive must precede all other elements defined in a Razor file.
+
+
+ Mark the page as a Razor Page.
+
+
+ An optional route template for the page.
+
+
+ RouteTemplate
+
+
\ No newline at end of file
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/TagHelperDescriptorExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/TagHelperDescriptorExtensions.cs
new file mode 100644
index 0000000000..b7bd72ec70
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/TagHelperDescriptorExtensions.cs
@@ -0,0 +1,37 @@
+// 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.Mvc.Razor.Extensions.Version2_X
+{
+ public static class TagHelperDescriptorExtensions
+ {
+ ///
+ /// Indicates whether a represents a view component.
+ ///
+ /// The to check.
+ /// Whether a represents a view component.
+ public static bool IsViewComponentKind(this TagHelperDescriptor tagHelper)
+ {
+ if (tagHelper == null)
+ {
+ throw new ArgumentNullException(nameof(tagHelper));
+ }
+
+ return string.Equals(ViewComponentTagHelperConventions.Kind, tagHelper.Kind, StringComparison.Ordinal);
+ }
+
+ public static string GetViewComponentName(this TagHelperDescriptor tagHelper)
+ {
+ if (tagHelper == null)
+ {
+ throw new ArgumentNullException(nameof(tagHelper));
+ }
+
+ tagHelper.Metadata.TryGetValue(ViewComponentTagHelperMetadata.Name, out var result);
+ return result;
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentResources.resx b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentResources.resx
new file mode 100644
index 0000000000..d6aad4ff3f
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentResources.resx
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ View component '{0}' must have exactly one public method named '{1}' or '{2}'.
+
+
+ Method '{0}' of view component '{1}' should be declared to return {2}<T>.
+
+
+ Could not find an '{0}' or '{1}' method for the view component '{2}'.
+
+
+ Method '{0}' of view component '{1}' cannot return a {2}.
+
+
+ Method '{0}' of view component '{1}' should be declared to return a value.
+
+
\ No newline at end of file
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperConventions.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperConventions.cs
new file mode 100644
index 0000000000..30e8d1c36f
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperConventions.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public static class ViewComponentTagHelperConventions
+ {
+ public static readonly string Kind = "MVC.ViewComponent";
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperDescriptorFactory.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperDescriptorFactory.cs
new file mode 100644
index 0000000000..0b1221af72
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperDescriptorFactory.cs
@@ -0,0 +1,284 @@
+// 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.Collections.Immutable;
+using System.Linq;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ internal class ViewComponentTagHelperDescriptorFactory
+ {
+ private readonly INamedTypeSymbol _viewComponentAttributeSymbol;
+ private readonly INamedTypeSymbol _genericTaskSymbol;
+ private readonly INamedTypeSymbol _taskSymbol;
+ private readonly INamedTypeSymbol _iDictionarySymbol;
+
+ private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
+ SymbolDisplayFormat.FullyQualifiedFormat
+ .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
+ .WithMiscellaneousOptions(SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions & (~SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
+
+ private static readonly IReadOnlyDictionary PrimitiveDisplayTypeNameLookups = new Dictionary(StringComparer.Ordinal)
+ {
+ [typeof(byte).FullName] = "byte",
+ [typeof(sbyte).FullName] = "sbyte",
+ [typeof(int).FullName] = "int",
+ [typeof(uint).FullName] = "uint",
+ [typeof(short).FullName] = "short",
+ [typeof(ushort).FullName] = "ushort",
+ [typeof(long).FullName] = "long",
+ [typeof(ulong).FullName] = "ulong",
+ [typeof(float).FullName] = "float",
+ [typeof(double).FullName] = "double",
+ [typeof(char).FullName] = "char",
+ [typeof(bool).FullName] = "bool",
+ [typeof(object).FullName] = "object",
+ [typeof(string).FullName] = "string",
+ [typeof(decimal).FullName] = "decimal",
+ };
+
+ public ViewComponentTagHelperDescriptorFactory(Compilation compilation)
+ {
+ _viewComponentAttributeSymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute);
+ _genericTaskSymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.GenericTask);
+ _taskSymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.Task);
+ _iDictionarySymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.IDictionary);
+ }
+
+ public virtual TagHelperDescriptor CreateDescriptor(INamedTypeSymbol type)
+ {
+ var assemblyName = type.ContainingAssembly.Name;
+ var shortName = GetShortName(type);
+ var tagName = $"vc:{HtmlConventions.ToHtmlCase(shortName)}";
+ var typeName = $"__Generated__{shortName}ViewComponentTagHelper";
+ var displayName = shortName + "ViewComponentTagHelper";
+ var descriptorBuilder = TagHelperDescriptorBuilder.Create(ViewComponentTagHelperConventions.Kind, typeName, assemblyName);
+ descriptorBuilder.SetTypeName(typeName);
+ descriptorBuilder.DisplayName = displayName;
+
+ if (TryFindInvokeMethod(type, out var method, out var diagnostic))
+ {
+ var methodParameters = method.Parameters;
+ descriptorBuilder.TagMatchingRule(ruleBuilder =>
+ {
+ ruleBuilder.TagName = tagName;
+ AddRequiredAttributes(methodParameters, ruleBuilder);
+ });
+
+ AddBoundAttributes(methodParameters, displayName, descriptorBuilder);
+ }
+ else
+ {
+ descriptorBuilder.Diagnostics.Add(diagnostic);
+ }
+
+ descriptorBuilder.Metadata[ViewComponentTagHelperMetadata.Name] = shortName;
+
+ var descriptor = descriptorBuilder.Build();
+ return descriptor;
+ }
+
+ private bool TryFindInvokeMethod(INamedTypeSymbol type, out IMethodSymbol method, out RazorDiagnostic diagnostic)
+ {
+ var methods = GetInvokeMethods(type);
+
+ if (methods.Count == 0)
+ {
+ diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_CannotFindMethod(type.ToDisplayString(FullNameTypeDisplayFormat));
+ method = null;
+ return false;
+ }
+ else if (methods.Count > 1)
+ {
+ diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_AmbiguousMethods(type.ToDisplayString(FullNameTypeDisplayFormat));
+ method = null;
+ return false;
+ }
+
+ var selectedMethod = methods[0];
+ var returnType = selectedMethod.ReturnType as INamedTypeSymbol;
+ if (string.Equals(selectedMethod.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal))
+ {
+ // Will invoke asynchronously. Method must not return Task or Task.
+ if (returnType == _taskSymbol)
+ {
+ // This is ok.
+ }
+ else if (returnType.IsGenericType && returnType.ConstructedFrom == _genericTaskSymbol)
+ {
+ // This is ok.
+ }
+ else
+ {
+ diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_AsyncMethod_ShouldReturnTask(type.ToDisplayString(FullNameTypeDisplayFormat));
+ method = null;
+ return false;
+ }
+ }
+ else
+ {
+ // Will invoke synchronously. Method must not return void, Task or Task.
+ if (returnType.SpecialType == SpecialType.System_Void)
+ {
+ diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_SyncMethod_ShouldReturnValue(type.ToDisplayString(FullNameTypeDisplayFormat));
+ method = null;
+ return false;
+ }
+ else if (returnType == _taskSymbol)
+ {
+ diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_SyncMethod_CannotReturnTask(type.ToDisplayString(FullNameTypeDisplayFormat));
+ method = null;
+ return false;
+ }
+ else if (returnType.IsGenericType && returnType.ConstructedFrom == _genericTaskSymbol)
+ {
+ diagnostic = RazorExtensionsDiagnosticFactory.CreateViewComponent_SyncMethod_CannotReturnTask(type.ToDisplayString(FullNameTypeDisplayFormat));
+ method = null;
+ return false;
+ }
+ }
+
+ method = selectedMethod;
+ diagnostic = null;
+ return true;
+ }
+
+ private static IReadOnlyList GetInvokeMethods(INamedTypeSymbol type)
+ {
+ var methods = new List();
+ while (type != null)
+ {
+ var currentTypeMethods = type.GetMembers()
+ .OfType()
+ .Where(m =>
+ m.DeclaredAccessibility == Accessibility.Public &&
+ !m.IsStatic &&
+ (string.Equals(m.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal) ||
+ string.Equals(m.Name, ViewComponentTypes.SyncMethodName, StringComparison.Ordinal)));
+
+ methods.AddRange(currentTypeMethods);
+
+ type = type.BaseType;
+ }
+
+ return methods;
+ }
+
+ private void AddRequiredAttributes(ImmutableArray methodParameters, TagMatchingRuleDescriptorBuilder builder)
+ {
+ foreach (var parameter in methodParameters)
+ {
+ if (GetIndexerValueTypeName(parameter) == null)
+ {
+ // Set required attributes only for non-indexer attributes. Indexer attributes can't be required attributes
+ // because there are two ways of setting values for the attribute.
+ builder.Attribute(attributeBuilder =>
+ {
+ var lowerKebabName = HtmlConventions.ToHtmlCase(parameter.Name);
+ attributeBuilder.Name = lowerKebabName;
+ });
+ }
+ }
+ }
+
+ private void AddBoundAttributes(ImmutableArray methodParameters, string containingDisplayName, TagHelperDescriptorBuilder builder)
+ {
+ foreach (var parameter in methodParameters)
+ {
+ var lowerKebabName = HtmlConventions.ToHtmlCase(parameter.Name);
+ var typeName = parameter.Type.ToDisplayString(FullNameTypeDisplayFormat);
+
+ if (!PrimitiveDisplayTypeNameLookups.TryGetValue(typeName, out var simpleName))
+ {
+ simpleName = typeName;
+ }
+
+ builder.BindAttribute(attributeBuilder =>
+ {
+ attributeBuilder.Name = lowerKebabName;
+ attributeBuilder.TypeName = typeName;
+ attributeBuilder.DisplayName = $"{simpleName} {containingDisplayName}.{parameter.Name}";
+ attributeBuilder.SetPropertyName(parameter.Name);
+
+ if (parameter.Type.TypeKind == TypeKind.Enum)
+ {
+ attributeBuilder.IsEnum = true;
+ }
+ else
+ {
+ var dictionaryValueType = GetIndexerValueTypeName(parameter);
+ if (dictionaryValueType != null)
+ {
+ attributeBuilder.AsDictionary(lowerKebabName + "-", dictionaryValueType);
+ }
+ }
+ });
+ }
+ }
+
+ private string GetIndexerValueTypeName(IParameterSymbol parameter)
+ {
+ INamedTypeSymbol dictionaryType;
+ if ((parameter.Type as INamedTypeSymbol)?.ConstructedFrom == _iDictionarySymbol)
+ {
+ dictionaryType = (INamedTypeSymbol)parameter.Type;
+ }
+ else if (parameter.Type.AllInterfaces.Any(s => s.ConstructedFrom == _iDictionarySymbol))
+ {
+ dictionaryType = parameter.Type.AllInterfaces.First(s => s.ConstructedFrom == _iDictionarySymbol);
+ }
+ else
+ {
+ dictionaryType = null;
+ }
+
+ if (dictionaryType == null || dictionaryType.TypeArguments[0].SpecialType != SpecialType.System_String)
+ {
+ return null;
+ }
+
+ var type = dictionaryType.TypeArguments[1];
+ var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
+
+ return typeName;
+ }
+
+ private string GetShortName(INamedTypeSymbol componentType)
+ {
+ var viewComponentAttribute = componentType.GetAttributes().Where(a => a.AttributeClass == _viewComponentAttributeSymbol).FirstOrDefault();
+ var name = viewComponentAttribute
+ ?.NamedArguments
+ .Where(namedArgument => string.Equals(namedArgument.Key, ViewComponentTypes.ViewComponent.Name, StringComparison.Ordinal))
+ .FirstOrDefault()
+ .Value
+ .Value as string;
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ var separatorIndex = name.LastIndexOf('.');
+ if (separatorIndex >= 0)
+ {
+ return name.Substring(separatorIndex + 1);
+ }
+ else
+ {
+ return name;
+ }
+ }
+
+ // Get name by convention
+ if (componentType.Name.EndsWith(ViewComponentTypes.ViewComponentSuffix, StringComparison.OrdinalIgnoreCase))
+ {
+ return componentType.Name.Substring(0, componentType.Name.Length - ViewComponentTypes.ViewComponentSuffix.Length);
+ }
+ else
+ {
+ return componentType.Name;
+ }
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperDescriptorProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperDescriptorProvider.cs
new file mode 100644
index 0000000000..4806acc806
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperDescriptorProvider.cs
@@ -0,0 +1,72 @@
+// 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;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public sealed class ViewComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
+ {
+ public int Order { get; set; }
+
+ public void Execute(TagHelperDescriptorProviderContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var compilation = context.GetCompilation();
+ if (compilation == null)
+ {
+ // No compilation, nothing to do.
+ return;
+ }
+
+ var vcAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute);
+ var nonVCAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.NonViewComponentAttribute);
+ if (vcAttribute == null || vcAttribute.TypeKind == TypeKind.Error)
+ {
+ // Could not find attributes we care about in the compilation. Nothing to do.
+ return;
+ }
+
+ var types = new List();
+ var visitor = new ViewComponentTypeVisitor(vcAttribute, nonVCAttribute, types);
+
+ // We always visit the global namespace.
+ visitor.Visit(compilation.Assembly.GlobalNamespace);
+
+ foreach (var reference in compilation.References)
+ {
+ if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
+ {
+ if (IsTagHelperAssembly(assembly))
+ {
+ visitor.Visit(assembly.GlobalNamespace);
+ }
+ }
+ }
+
+ var factory = new ViewComponentTagHelperDescriptorFactory(compilation);
+ for (var i = 0; i < types.Count; i++)
+ {
+ var descriptor = factory.CreateDescriptor(types[i]);
+
+ if (descriptor != null)
+ {
+ context.Results.Add(descriptor);
+ }
+ }
+ }
+
+ private bool IsTagHelperAssembly(IAssemblySymbol assembly)
+ {
+ return assembly.Name != null && !assembly.Name.StartsWith("System.", StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperIntermediateNode.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperIntermediateNode.cs
new file mode 100644
index 0000000000..a42b88416b
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperIntermediateNode.cs
@@ -0,0 +1,59 @@
+// 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.Mvc.Razor.Extensions.Version2_X
+{
+ public sealed class ViewComponentTagHelperIntermediateNode : ExtensionIntermediateNode
+ {
+ public override IntermediateNodeCollection Children { get; } = IntermediateNodeCollection.ReadOnly;
+
+ public string ClassName { get; set; }
+
+ public TagHelperDescriptor TagHelper { get; set; }
+
+ public override void Accept(IntermediateNodeVisitor visitor)
+ {
+ if (visitor == null)
+ {
+ throw new ArgumentNullException(nameof(visitor));
+ }
+
+ AcceptExtensionNode(this, visitor);
+ }
+
+ public override void WriteNode(CodeTarget target, CodeRenderingContext context)
+ {
+ if (target == null)
+ {
+ throw new ArgumentNullException(nameof(target));
+ }
+
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var extension = target.GetExtension();
+ if (extension == null)
+ {
+ ReportMissingCodeTargetExtension(context);
+ return;
+ }
+
+ extension.WriteViewComponentTagHelper(context, this);
+ }
+
+ public override void FormatNode(IntermediateNodeFormatter formatter)
+ {
+ formatter.WriteContent(ClassName);
+
+ formatter.WriteProperty(nameof(ClassName), ClassName);
+ formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperMetadata.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperMetadata.cs
new file mode 100644
index 0000000000..ad77c7a29e
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperMetadata.cs
@@ -0,0 +1,14 @@
+// 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.Mvc.Razor.Extensions.Version2_X
+{
+ public static class ViewComponentTagHelperMetadata
+ {
+ ///
+ /// The key in a containing
+ /// the short name of a view component.
+ ///
+ public static readonly string Name = "MVC.ViewComponent.Name";
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperPass.cs
new file mode 100644
index 0000000000..f5c37eb13b
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperPass.cs
@@ -0,0 +1,203 @@
+// 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.AspNetCore.Razor.Language.Extensions;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ public class ViewComponentTagHelperPass : IntermediateNodePassBase, IRazorOptimizationPass
+ {
+ // Run after the default taghelper pass
+ public override int Order => IntermediateNodePassBase.DefaultFeatureOrder + 2000;
+
+ 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 context = new Context(@namespace, @class);
+
+ // For each VCTH *usage* we need to rewrite the tag helper node to use the tag helper runtime to construct
+ // and set properties on the the correct field, and using the name of the type we will generate.
+ var nodes = documentNode.FindDescendantNodes();
+ for (var i = 0; i < nodes.Count; i++)
+ {
+ var node = nodes[i];
+ foreach (var tagHelper in node.TagHelpers)
+ {
+ RewriteUsage(context, node, tagHelper);
+ }
+ }
+
+ // Then for each VCTH *definition* that we've seen we need to generate the class that implements
+ // ITagHelper and the field that will hold it.
+ foreach (var tagHelper in context.TagHelpers)
+ {
+ AddField(context, tagHelper);
+ AddTagHelperClass(context, tagHelper);
+ }
+ }
+
+ private void RewriteUsage(Context context, TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
+ {
+ if (!tagHelper.IsViewComponentKind())
+ {
+ return;
+ }
+
+ context.Add(tagHelper);
+
+ // Now we need to insert a create node using the default tag helper runtime. This is similar to
+ // code in DefaultTagHelperOptimizationPass.
+ //
+ // Find the body node.
+ var i = 0;
+ while (i < node.Children.Count && node.Children[i] is TagHelperBodyIntermediateNode)
+ {
+ i++;
+ }
+ while (i < node.Children.Count && node.Children[i] is DefaultTagHelperBodyIntermediateNode)
+ {
+ i++;
+ }
+
+ // Now find the last create node.
+ while (i < node.Children.Count && node.Children[i] is DefaultTagHelperCreateIntermediateNode)
+ {
+ i++;
+ }
+
+ // Now i has the right insertion point.
+ node.Children.Insert(i, new DefaultTagHelperCreateIntermediateNode()
+ {
+ FieldName = context.GetFieldName(tagHelper),
+ TagHelper = tagHelper,
+ TypeName = context.GetFullyQualifiedName(tagHelper),
+ });
+
+ // Now we need to rewrite any set property nodes to use the default runtime.
+ for (i = 0; i < node.Children.Count; i++)
+ {
+ if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode &&
+ propertyNode.TagHelper == tagHelper)
+ {
+ // This is a set property for this VCTH - we need to replace it with a node
+ // that will use our field and property name.
+ node.Children[i] = new DefaultTagHelperPropertyIntermediateNode(propertyNode)
+ {
+ FieldName = context.GetFieldName(tagHelper),
+ PropertyName = propertyNode.BoundAttribute.GetPropertyName(),
+ };
+ }
+ }
+ }
+
+ private void AddField(Context context, TagHelperDescriptor tagHelper)
+ {
+ // We need to insert a node for the field that will hold the tag helper. We've already generated a field name
+ // at this time and use it for all uses of the same tag helper type.
+ //
+ // We also want to preserve the ordering of the nodes for testability. So insert at the end of any existing
+ // field nodes.
+ var i = 0;
+ while (i < context.Class.Children.Count && context.Class.Children[i] is DefaultTagHelperRuntimeIntermediateNode)
+ {
+ i++;
+ }
+
+ while (i < context.Class.Children.Count && context.Class.Children[i] is FieldDeclarationIntermediateNode)
+ {
+ i++;
+ }
+
+ context.Class.Children.Insert(i, new FieldDeclarationIntermediateNode()
+ {
+ Annotations =
+ {
+ { CommonAnnotations.DefaultTagHelperExtension.TagHelperField, bool.TrueString },
+ },
+ Modifiers =
+ {
+ "private",
+ },
+ FieldName = context.GetFieldName(tagHelper),
+ FieldType = "global::" + context.GetFullyQualifiedName(tagHelper),
+ });
+ }
+
+ private void AddTagHelperClass(Context context, TagHelperDescriptor tagHelper)
+ {
+ var node = new ViewComponentTagHelperIntermediateNode()
+ {
+ ClassName = context.GetClassName(tagHelper),
+ TagHelper = tagHelper
+ };
+
+ context.Class.Children.Add(node);
+ }
+
+ private struct Context
+ {
+ private Dictionary _tagHelpers;
+
+ public Context(NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class)
+ {
+ Namespace = @namespace;
+ Class = @class;
+
+ _tagHelpers = new Dictionary();
+ }
+
+ public ClassDeclarationIntermediateNode Class { get; }
+
+ public NamespaceDeclarationIntermediateNode Namespace { get; }
+
+
+ public IEnumerable TagHelpers => _tagHelpers.Keys;
+
+ public bool Add(TagHelperDescriptor tagHelper)
+ {
+ if (_tagHelpers.ContainsKey(tagHelper))
+ {
+ return false;
+ }
+
+ var className = $"__Generated__{tagHelper.GetViewComponentName()}ViewComponentTagHelper";
+ var fullyQualifiedName = $"{Namespace.Content}.{Class.ClassName}.{className}";
+ var fieldName = GenerateFieldName(tagHelper);
+
+ _tagHelpers.Add(tagHelper, (className, fullyQualifiedName, fieldName));
+
+ return true;
+ }
+
+ public string GetClassName(TagHelperDescriptor taghelper)
+ {
+ return _tagHelpers[taghelper].className;
+ }
+
+ public string GetFullyQualifiedName(TagHelperDescriptor taghelper)
+ {
+ return _tagHelpers[taghelper].fullyQualifiedName;
+ }
+
+ public string GetFieldName(TagHelperDescriptor taghelper)
+ {
+ return _tagHelpers[taghelper].fieldName;
+ }
+
+ private static string GenerateFieldName(TagHelperDescriptor tagHelper)
+ {
+ return $"__{tagHelper.GetViewComponentName()}ViewComponentTagHelper";
+ }
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperTargetExtension.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperTargetExtension.cs
new file mode 100644
index 0000000000..a56e86fdca
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTagHelperTargetExtension.cs
@@ -0,0 +1,183 @@
+// 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.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ internal class ViewComponentTagHelperTargetExtension : IViewComponentTagHelperTargetExtension
+ {
+ private static readonly string[] PublicModifiers = new[] { "public" };
+
+ public string TagHelperTypeName { get; set; } = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper";
+
+ public string ViewComponentHelperTypeName { get; set; } = "global::Microsoft.AspNetCore.Mvc.IViewComponentHelper";
+
+ public string ViewComponentHelperVariableName { get; set; } = "_helper";
+
+ public string ViewComponentInvokeMethodName { get; set; } = "InvokeAsync";
+
+ public string HtmlAttributeNotBoundAttributeTypeName { get; set; } = "Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute";
+
+ public string ViewContextAttributeTypeName { get; set; } = "global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute";
+
+ public string ViewContextTypeName { get; set; } = "global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext";
+
+ public string ViewContextPropertyName { get; set; } = "ViewContext";
+
+ public string HtmlTargetElementAttributeTypeName { get; set; } = "Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute";
+
+ public string TagHelperProcessMethodName { get; set; } = "ProcessAsync";
+
+ public string TagHelperContextTypeName { get; set; } = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext";
+
+ public string TagHelperContextVariableName { get; set; } = "context";
+
+ public string TagHelperOutputTypeName { get; set; } = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput";
+
+ public string TagHelperOutputVariableName { get; set; } = "output";
+
+ public string TagHelperOutputTagNamePropertyName { get; set; } = "TagName";
+
+ public string TagHelperOutputContentPropertyName { get; set; } = "Content";
+
+ public string TagHelperContentSetMethodName { get; set; } = "SetHtmlContent";
+
+ public string TagHelperContentVariableName { get; set; } = "content";
+
+ public string IViewContextAwareTypeName { get; set; } = "global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware";
+
+ public string IViewContextAwareContextualizeMethodName { get; set; } = "Contextualize";
+
+ public void WriteViewComponentTagHelper(CodeRenderingContext context, ViewComponentTagHelperIntermediateNode node)
+ {
+ // Add target element.
+ WriteTargetElementString(context.CodeWriter, node.TagHelper);
+
+ // Initialize declaration.
+ using (context.CodeWriter.BuildClassDeclaration(
+ PublicModifiers,
+ node.ClassName,
+ TagHelperTypeName,
+ interfaces: null,
+ typeParameters: null))
+ {
+ // Add view component helper.
+ context.CodeWriter.WriteVariableDeclaration(
+ $"private readonly {ViewComponentHelperTypeName}",
+ ViewComponentHelperVariableName,
+ value: null);
+
+ // Add constructor.
+ WriteConstructorString(context.CodeWriter, node.ClassName);
+
+ // Add attributes.
+ WriteAttributeDeclarations(context.CodeWriter, node.TagHelper);
+
+ // Add process method.
+ WriteProcessMethodString(context.CodeWriter, node.TagHelper);
+ }
+ }
+
+ private void WriteConstructorString(CodeWriter writer, string className)
+ {
+ writer.Write("public ")
+ .Write(className)
+ .Write("(")
+ .Write($"{ViewComponentHelperTypeName} helper")
+ .WriteLine(")");
+ using (writer.BuildScope())
+ {
+ writer.WriteStartAssignment(ViewComponentHelperVariableName)
+ .Write("helper")
+ .WriteLine(";");
+ }
+ }
+
+ private void WriteAttributeDeclarations(CodeWriter writer, TagHelperDescriptor tagHelper)
+ {
+ writer.Write("[")
+ .Write(HtmlAttributeNotBoundAttributeTypeName)
+ .WriteParameterSeparator()
+ .Write(ViewContextAttributeTypeName)
+ .WriteLine("]");
+
+ writer.WriteAutoPropertyDeclaration(
+ PublicModifiers,
+ ViewContextTypeName,
+ ViewContextPropertyName);
+
+ foreach (var attribute in tagHelper.BoundAttributes)
+ {
+ writer.WriteAutoPropertyDeclaration(
+ PublicModifiers,
+ attribute.TypeName,
+ attribute.GetPropertyName());
+
+ if (attribute.IndexerTypeName != null)
+ {
+ writer.Write(" = ")
+ .WriteStartNewObject(attribute.TypeName)
+ .WriteEndMethodInvocation();
+ }
+ }
+ }
+
+ private void WriteProcessMethodString(CodeWriter writer, TagHelperDescriptor tagHelper)
+ {
+ using (writer.BuildMethodDeclaration(
+ $"public override async",
+ $"global::{typeof(Task).FullName}",
+ TagHelperProcessMethodName,
+ new Dictionary()
+ {
+ { TagHelperContextTypeName, TagHelperContextVariableName },
+ { TagHelperOutputTypeName, TagHelperOutputVariableName }
+ }))
+ {
+ writer.WriteInstanceMethodInvocation(
+ $"({ViewComponentHelperVariableName} as {IViewContextAwareTypeName})?",
+ IViewContextAwareContextualizeMethodName,
+ new[] { ViewContextPropertyName });
+
+ var methodParameters = GetMethodParameters(tagHelper);
+ writer.Write("var ")
+ .WriteStartAssignment(TagHelperContentVariableName)
+ .WriteInstanceMethodInvocation($"await {ViewComponentHelperVariableName}", ViewComponentInvokeMethodName, methodParameters);
+ writer.WriteStartAssignment($"{TagHelperOutputVariableName}.{TagHelperOutputTagNamePropertyName}")
+ .WriteLine("null;");
+ writer.WriteInstanceMethodInvocation(
+ $"{TagHelperOutputVariableName}.{TagHelperOutputContentPropertyName}",
+ TagHelperContentSetMethodName,
+ new[] { TagHelperContentVariableName });
+ }
+ }
+
+ private string[] GetMethodParameters(TagHelperDescriptor tagHelper)
+ {
+ var propertyNames = tagHelper.BoundAttributes.Select(attribute => attribute.GetPropertyName());
+ var joinedPropertyNames = string.Join(", ", propertyNames);
+ var parametersString = $"new {{ { joinedPropertyNames } }}";
+ var viewComponentName = tagHelper.GetViewComponentName();
+ var methodParameters = new[] { $"\"{viewComponentName}\"", parametersString };
+ return methodParameters;
+ }
+
+ private void WriteTargetElementString(CodeWriter writer, TagHelperDescriptor tagHelper)
+ {
+ Debug.Assert(tagHelper.TagMatchingRules.Count() == 1);
+
+ var rule = tagHelper.TagMatchingRules.First();
+
+ writer.Write("[")
+ .WriteStartMethodInvocation(HtmlTargetElementAttributeTypeName)
+ .WriteStringLiteral(rule.TagName)
+ .WriteLine(")]");
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTypeVisitor.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTypeVisitor.cs
new file mode 100644
index 0000000000..01eb3fffaa
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTypeVisitor.cs
@@ -0,0 +1,88 @@
+// 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.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ internal class ViewComponentTypeVisitor : SymbolVisitor
+ {
+ private readonly INamedTypeSymbol _viewComponentAttribute;
+ private readonly INamedTypeSymbol _nonViewComponentAttribute;
+ private readonly List _results;
+
+ public ViewComponentTypeVisitor(
+ INamedTypeSymbol viewComponentAttribute,
+ INamedTypeSymbol nonViewComponentAttribute,
+ List results)
+ {
+ _viewComponentAttribute = viewComponentAttribute;
+ _nonViewComponentAttribute = nonViewComponentAttribute;
+ _results = results;
+ }
+
+ public override void VisitNamedType(INamedTypeSymbol symbol)
+ {
+ if (IsViewComponent(symbol))
+ {
+ _results.Add(symbol);
+ }
+
+ if (symbol.DeclaredAccessibility != Accessibility.Public)
+ {
+ return;
+ }
+
+ foreach (var member in symbol.GetTypeMembers())
+ {
+ Visit(member);
+ }
+ }
+
+ public override void VisitNamespace(INamespaceSymbol symbol)
+ {
+ foreach (var member in symbol.GetMembers())
+ {
+ Visit(member);
+ }
+ }
+
+ internal bool IsViewComponent(INamedTypeSymbol symbol)
+ {
+ if (_viewComponentAttribute == null)
+ {
+ return false;
+ }
+
+ if (symbol.DeclaredAccessibility != Accessibility.Public ||
+ symbol.IsAbstract ||
+ symbol.IsGenericType ||
+ AttributeIsDefined(symbol, _nonViewComponentAttribute))
+ {
+ return false;
+ }
+
+ return symbol.Name.EndsWith(ViewComponentTypes.ViewComponentSuffix) ||
+ AttributeIsDefined(symbol, _viewComponentAttribute);
+ }
+
+ private static bool AttributeIsDefined(INamedTypeSymbol type, INamedTypeSymbol queryAttribute)
+ {
+ if (type == null || queryAttribute == null)
+ {
+ return false;
+ }
+
+ var attribute = type.GetAttributes().Where(a => a.AttributeClass == queryAttribute).FirstOrDefault();
+
+ if (attribute != null)
+ {
+ return true;
+ }
+
+ return AttributeIsDefined(type.BaseType, queryAttribute);
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTypes.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTypes.cs
new file mode 100644
index 0000000000..cd7e2771be
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/ViewComponentTypes.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X
+{
+ internal static class ViewComponentTypes
+ {
+ public const string Assembly = "Microsoft.AspNetCore.Mvc.ViewFeatures";
+
+ public static readonly Version AssemblyVersion = new Version(1, 1, 0, 0);
+
+ public const string ViewComponentSuffix = "ViewComponent";
+
+ public const string ViewComponentAttribute = "Microsoft.AspNetCore.Mvc.ViewComponentAttribute";
+
+ public const string NonViewComponentAttribute = "Microsoft.AspNetCore.Mvc.NonViewComponentAttribute";
+
+ public const string GenericTask = "System.Threading.Tasks.Task`1";
+
+ public const string Task = "System.Threading.Tasks.Task";
+
+ public const string IDictionary = "System.Collections.Generic.IDictionary`2";
+
+ public const string AsyncMethodName = "InvokeAsync";
+
+ public const string SyncMethodName = "Invoke";
+
+ public static class ViewComponent
+ {
+ public const string Name = "Name";
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/baseline.netcore.json b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/baseline.netcore.json
new file mode 100644
index 0000000000..87b3d074f3
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/baseline.netcore.json
@@ -0,0 +1,1180 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.AssemblyAttributeInjectionPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IInjectTargetExtension",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.CodeGeneration.ICodeTargetExtension"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "WriteInjectProperty",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ },
+ {
+ "Name": "node",
+ "Type": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectIntermediateNode",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.Intermediate.ExtensionIntermediateNode",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_TypeName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_TypeName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_MemberName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_MemberName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Children",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeCollection",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Accept",
+ "Parameters": [
+ {
+ "Name": "visitor",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeVisitor"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WriteNode",
+ "Parameters": [
+ {
+ "Name": "target",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeTarget"
+ },
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectTargetExtension",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IInjectTargetExtension"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "WriteInjectProperty",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ },
+ {
+ "Name": "node",
+ "Type": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IInjectTargetExtension",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InstrumentationPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Order",
+ "Parameters": [],
+ "ReturnType": "System.Int32",
+ "Virtual": true,
+ "Override": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IViewComponentTagHelperTargetExtension",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.CodeGeneration.ICodeTargetExtension"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "WriteViewComponentTagHelper",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ },
+ {
+ "Name": "node",
+ "Type": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ModelDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetModelType",
+ "Parameters": [
+ {
+ "Name": "document",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ModelExpressionPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.MvcViewDocumentClassifierPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.DocumentClassifierPassBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_DocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "IsMatch",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "OnDocumentStructureCreated",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "namespace",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.NamespaceDeclarationIntermediateNode"
+ },
+ {
+ "Name": "class",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.ClassDeclarationIntermediateNode"
+ },
+ {
+ "Name": "method",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.MethodDeclarationIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "MvcViewDocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.NamespaceDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.PageDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_RouteTemplate",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DirectiveNode",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNode",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "TryGetPageDirective",
+ "Parameters": [
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ },
+ {
+ "Name": "pageDirective",
+ "Type": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.PageDirective",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.PagesPropertyInjectionPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.RazorExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.RazorPageDocumentClassifierPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.DocumentClassifierPassBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_DocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "IsMatch",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "OnDocumentStructureCreated",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "namespace",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.NamespaceDeclarationIntermediateNode"
+ },
+ {
+ "Name": "class",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.ClassDeclarationIntermediateNode"
+ },
+ {
+ "Name": "method",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.MethodDeclarationIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "RazorPageDocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "RouteTemplateKey",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.TagHelperDescriptorExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "IsViewComponentKind",
+ "Parameters": [
+ {
+ "Name": "tagHelper",
+ "Type": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptor"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetViewComponentName",
+ "Parameters": [
+ {
+ "Name": "tagHelper",
+ "Type": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptor"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperConventions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Kind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperDescriptorProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Sealed": true,
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.RazorEngineFeatureBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.ITagHelperDescriptorProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Order",
+ "Parameters": [],
+ "ReturnType": "System.Int32",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.ITagHelperDescriptorProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Order",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Execute",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptorProviderContext"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.ITagHelperDescriptorProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperIntermediateNode",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Sealed": true,
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.Intermediate.ExtensionIntermediateNode",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Children",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeCollection",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ClassName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ClassName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_TagHelper",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptor",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_TagHelper",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptor"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Accept",
+ "Parameters": [
+ {
+ "Name": "visitor",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeVisitor"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WriteNode",
+ "Parameters": [
+ {
+ "Name": "target",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeTarget"
+ },
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperMetadata",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Name",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Order",
+ "Parameters": [],
+ "ReturnType": "System.Int32",
+ "Virtual": true,
+ "Override": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriterExtensions+CSharpCodeWritingScope",
+ "Visibility": "Public",
+ "Kind": "Struct",
+ "Sealed": true,
+ "ImplementedInterfaces": [
+ "System.IDisposable"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Dispose",
+ "Parameters": [],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "System.IDisposable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "writer",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriter"
+ },
+ {
+ "Name": "tabSize",
+ "Type": "System.Int32",
+ "DefaultValue": "4"
+ },
+ {
+ "Name": "autoSpace",
+ "Type": "System.Boolean",
+ "DefaultValue": "True"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTypes+ViewComponent",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Name",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": [],
+ "Constant": true,
+ "Literal": "\"Name\""
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/baseline.netframework.json b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/baseline.netframework.json
new file mode 100644
index 0000000000..9386496226
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/baseline.netframework.json
@@ -0,0 +1,982 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.AssemblyAttributeInjectionPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IInjectTargetExtension",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.CodeGeneration.ICodeTargetExtension"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "WriteInjectProperty",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ },
+ {
+ "Name": "node",
+ "Type": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectIntermediateNode",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.Intermediate.ExtensionIntermediateNode",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_TypeName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_TypeName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_MemberName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_MemberName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Children",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeCollection",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Accept",
+ "Parameters": [
+ {
+ "Name": "visitor",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeVisitor"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "WriteNode",
+ "Parameters": [
+ {
+ "Name": "target",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeTarget"
+ },
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectTargetExtension",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IInjectTargetExtension"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "WriteInjectProperty",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeRenderingContext"
+ },
+ {
+ "Name": "node",
+ "Type": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InjectIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.IInjectTargetExtension",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.InstrumentationPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Order",
+ "Parameters": [],
+ "ReturnType": "System.Int32",
+ "Virtual": true,
+ "Override": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ModelDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetModelType",
+ "Parameters": [
+ {
+ "Name": "document",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ModelExpressionPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.MvcViewDocumentClassifierPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.DocumentClassifierPassBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_DocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "IsMatch",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "OnDocumentStructureCreated",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "namespace",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.NamespaceDeclarationIntermediateNode"
+ },
+ {
+ "Name": "class",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.ClassDeclarationIntermediateNode"
+ },
+ {
+ "Name": "method",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.MethodDeclarationIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "MvcViewDocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.NamespaceDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.PageDirective",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_RouteTemplate",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DirectiveNode",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNode",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "TryGetPageDirective",
+ "Parameters": [
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ },
+ {
+ "Name": "pageDirective",
+ "Type": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.PageDirective",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "Directive",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Razor.Language.DirectiveDescriptor",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.PagesPropertyInjectionPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.RazorExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Register",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectEngineBuilder"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.RazorPageDocumentClassifierPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.DocumentClassifierPassBase",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_DocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "IsMatch",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "OnDocumentStructureCreated",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "namespace",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.NamespaceDeclarationIntermediateNode"
+ },
+ {
+ "Name": "class",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.ClassDeclarationIntermediateNode"
+ },
+ {
+ "Name": "method",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.MethodDeclarationIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Field",
+ "Name": "RazorPageDocumentKind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.TagHelperDescriptorExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "IsViewComponentKind",
+ "Parameters": [
+ {
+ "Name": "tagHelper",
+ "Type": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptor"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetViewComponentName",
+ "Parameters": [
+ {
+ "Name": "tagHelper",
+ "Type": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptor"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperConventions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Kind",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperDescriptorProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Sealed": true,
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.RazorEngineFeatureBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.ITagHelperDescriptorProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Order",
+ "Parameters": [],
+ "ReturnType": "System.Int32",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.ITagHelperDescriptorProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Order",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Execute",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Razor.Language.TagHelperDescriptorProviderContext"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.ITagHelperDescriptorProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperMetadata",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Name",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "ReadOnly": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTagHelperPass",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "BaseType": "Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Order",
+ "Parameters": [],
+ "ReturnType": "System.Int32",
+ "Virtual": true,
+ "Override": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ExecuteCore",
+ "Parameters": [
+ {
+ "Name": "codeDocument",
+ "Type": "Microsoft.AspNetCore.Razor.Language.RazorCodeDocument"
+ },
+ {
+ "Name": "documentNode",
+ "Type": "Microsoft.AspNetCore.Razor.Language.Intermediate.DocumentIntermediateNode"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "Override": true,
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriterExtensions+CSharpCodeWritingScope",
+ "Visibility": "Public",
+ "Kind": "Struct",
+ "Sealed": true,
+ "ImplementedInterfaces": [
+ "System.IDisposable"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Dispose",
+ "Parameters": [],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "System.IDisposable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "writer",
+ "Type": "Microsoft.AspNetCore.Razor.Language.CodeGeneration.CodeWriter"
+ },
+ {
+ "Name": "tabSize",
+ "Type": "System.Int32",
+ "DefaultValue": "4"
+ },
+ {
+ "Name": "autoSpace",
+ "Type": "System.Boolean",
+ "DefaultValue": "True"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.ViewComponentTypes+ViewComponent",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "Name",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": [],
+ "Constant": true,
+ "Literal": "\"Name\""
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ExtensionInitializer.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ExtensionInitializer.cs
index eeb3246f74..8c650ebedb 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ExtensionInitializer.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ExtensionInitializer.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.AspNetCore.Razor.Language.Components;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs
index fcb874dc29..4282ea559f 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs
@@ -44,6 +44,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
+ if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind &&
+ documentNode.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind)
+ {
+ // Not a MVC file. Skip.
+ return;
+ }
+
var visitor = new Visitor();
visitor.Visit(documentNode);
var modelType = ModelDirective.GetModelType(documentNode);
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj
index 401d07c030..afa5ecd679 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj
@@ -2,19 +2,14 @@
ASP.NET Core design time hosting infrastructure for the Razor view engine.
- net46;netstandard2.0
+ netstandard2.0
$(PackageTags);aspnetcoremvc
-
- Shared\CodeWriterExtensions.cs
-
-
-
-
-
-
+
+
+
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs
index 57e9ad23a2..975cf90666 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@@ -31,12 +30,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
// It's possible for a Razor document to not have a file path.
// Eg. When we try to generate code for an in memory document like default imports.
- var checksum = BytesToString(codeDocument.Source.GetChecksum());
+ var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
@class.ClassName = $"AspNetCore_{checksum}";
}
else
{
- @class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath);
+ @class.ClassName = GetClassNameFromPath(filePath);
}
@class.BaseType = "global::Microsoft.AspNetCore.Mvc.Razor.RazorPage";
@@ -51,21 +50,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}";
}
- private static string BytesToString(byte[] bytes)
+ private static string GetClassNameFromPath(string path)
{
- if (bytes == null)
+ const string cshtmlExtension = ".cshtml";
+
+ if (string.IsNullOrEmpty(path))
{
- throw new ArgumentNullException(nameof(bytes));
+ return path;
}
- var result = new StringBuilder(bytes.Length);
- for (var i = 0; i < bytes.Length; i++)
+ if (path.EndsWith(cshtmlExtension, StringComparison.OrdinalIgnoreCase))
{
- // The x2 format means lowercase hex, where each byte is a 2-character string.
- result.Append(bytes[i].ToString("x2"));
+ path = path.Substring(0, path.Length - cshtmlExtension.Length);
}
- return result.ToString();
+ return CSharpIdentifier.SanitizeIdentifier(path);
}
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs
index ea3bc40bc1..1a809c0629 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs
@@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
for (var i = 0; i < segments.Length - 1; i++)
{
builder.Append('.');
- builder.Append(CSharpIdentifier.SanitizeClassName(segments[i]));
+ builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
}
return builder.ToString();
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/AssemblyInfo.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/AssemblyInfo.cs
index b5caca17dd..7d8a47bc67 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/AssemblyInfo.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Properties/AssemblyInfo.cs
@@ -5,8 +5,7 @@ using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
-[assembly: ProvideRazorExtensionInitializer("MVC-2.0", typeof(ExtensionInitializer))]
-[assembly: ProvideRazorExtensionInitializer("MVC-2.1", typeof(ExtensionInitializer))]
+[assembly: ProvideRazorExtensionInitializer("MVC-3.0", typeof(ExtensionInitializer))]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
index ff5fcaad68..04febb17d6 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
@@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
NamespaceDirective.Register(builder);
PageDirective.Register(builder);
- FunctionsDirective.Register(builder);
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
@@ -41,50 +40,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
builder.Features.Add(new RazorPageDocumentClassifierPass());
builder.Features.Add(new MvcViewDocumentClassifierPass());
builder.Features.Add(new AssemblyAttributeInjectionPass());
- builder.Features.Add(new InstrumentationPass());
builder.SetImportFeature(new MvcImportProjectFeature());
}
-
- #region Obsolete
- [Obsolete("This method is obsolete and will be removed in a future version.")]
- public static void Register(IRazorEngineBuilder builder)
- {
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- InjectDirective.Register(builder);
- ModelDirective.Register(builder);
- NamespaceDirective.Register(builder);
- PageDirective.Register(builder);
-
- FunctionsDirective.Register(builder);
- InheritsDirective.Register(builder);
- SectionDirective.Register(builder);
-
- builder.Features.Add(new DefaultTagHelperDescriptorProvider());
- builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
-
- builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
- builder.AddTargetExtension(new TemplateTargetExtension()
- {
- TemplateTypeName = "global::Microsoft.AspNetCore.Mvc.Razor.HelperResult",
- });
-
- builder.Features.Add(new ModelExpressionPass());
- builder.Features.Add(new PagesPropertyInjectionPass());
- builder.Features.Add(new ViewComponentTagHelperPass());
- builder.Features.Add(new RazorPageDocumentClassifierPass());
- builder.Features.Add(new MvcViewDocumentClassifierPass());
-
- if (!builder.DesignTime)
- {
- builder.Features.Add(new AssemblyAttributeInjectionPass());
- builder.Features.Add(new InstrumentationPass());
- }
- }
- #endregion
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs
index 9af7873874..c6c0df9345 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs
@@ -3,7 +3,6 @@
using System;
using System.Diagnostics;
-using System.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@@ -58,12 +57,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
// It's possible for a Razor document to not have a file path.
// Eg. When we try to generate code for an in memory document like default imports.
- var checksum = BytesToString(codeDocument.Source.GetChecksum());
+ var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
@class.ClassName = $"AspNetCore_{checksum}";
}
else
{
- @class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath);
+ @class.ClassName = GetClassNameFromPath(filePath);
}
@class.Modifiers.Clear();
@@ -144,21 +143,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
}
}
- private static string BytesToString(byte[] bytes)
+ private static string GetClassNameFromPath(string path)
{
- if (bytes == null)
+ const string cshtmlExtension = ".cshtml";
+
+ if (string.IsNullOrEmpty(path))
{
- throw new ArgumentNullException(nameof(bytes));
+ return path;
}
- var result = new StringBuilder(bytes.Length);
- for (var i = 0; i < bytes.Length; i++)
+ if (path.EndsWith(cshtmlExtension, StringComparison.OrdinalIgnoreCase))
{
- // The x2 format means lowercase hex, where each byte is a 2-character string.
- result.Append(bytes[i].ToString("x2"));
+ path = path.Substring(0, path.Length - cshtmlExtension.Length);
}
- return result.ToString();
+ return CSharpIdentifier.SanitizeIdentifier(path);
}
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/breakingchanges.netcore.json b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/breakingchanges.netcore.json
new file mode 100644
index 0000000000..5f5b6444f6
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/breakingchanges.netcore.json
@@ -0,0 +1,11 @@
+[
+ {
+ "TypeId": "public static class Microsoft.AspNetCore.Mvc.Razor.Extensions.RazorExtensions",
+ "MemberId": "public static System.Void Register(Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder builder)",
+ "Kind": "Removal"
+ },
+ {
+ "TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Extensions.InstrumentationPass : Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase, Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass",
+ "Kind": "Removal"
+ }
+]
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/breakingchanges.netframework.json b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/breakingchanges.netframework.json
new file mode 100644
index 0000000000..5f5b6444f6
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/breakingchanges.netframework.json
@@ -0,0 +1,11 @@
+[
+ {
+ "TypeId": "public static class Microsoft.AspNetCore.Mvc.Razor.Extensions.RazorExtensions",
+ "MemberId": "public static System.Void Register(Microsoft.AspNetCore.Razor.Language.IRazorEngineBuilder builder)",
+ "Kind": "Removal"
+ },
+ {
+ "TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Extensions.InstrumentationPass : Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase, Microsoft.AspNetCore.Razor.Language.IRazorOptimizationPass",
+ "Kind": "Removal"
+ }
+]
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.props b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.props
deleted file mode 100644
index c968ea7b3a..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.props
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
- MVC-2.1
-
-
- true
-
-
- <_MvcExtensionAssemblyPath Condition="'$(_MvcExtensionAssemblyPath)'==''">$(MSBuildThisFileDirectory)..\..\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll
-
-
-
-
-
- MVC-2.1;$(CustomRazorExtension)
-
-
-
-
-
- Microsoft.AspNetCore.Mvc.Razor.Extensions
- $(_MvcExtensionAssemblyPath)
-
-
-
diff --git a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.targets b/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.targets
deleted file mode 100644
index ffe642e283..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.targets
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
- true
-
-
- $(GenerateRazorAssemblyInfo)
-
-
- .Views
-
-
- Microsoft.AspNetCore.Mvc.ApplicationParts.CompiledRazorAssemblyApplicationPartFactory, Microsoft.AspNetCore.Mvc.Razor
-
-
-
- <_RazorAssemblyAttribute Include="Microsoft.AspNetCore.Mvc.ApplicationParts.RelatedAssemblyAttribute">
- <_Parameter1>$(RazorTargetName)
-
-
-
-
-
- <_Parameter1>$(ProvideApplicationPartFactoryAttributeTypeName)
-
-
-
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj b/src/Razor/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj
deleted file mode 100644
index 083a24e847..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.csproj
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
- Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor.
- netcoreapp2.0
-
-
- false
- false
- false
-
- $(MSBuildProjectName).nuspec
-
-
-
-
-
-
- ..\Microsoft.AspNetCore.Razor.Tools\Microsoft.AspNetCore.Razor.Tools.csproj
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
- unknown
-
-
- id=$(PackageId);
- version=$(PackageVersion);
- authors=$(Authors);
- description=$(Description);
- tags=$(PackageTags.Replace(';', ' '));
- licenseUrl=$(PackageLicenseUrl);
- projectUrl=$(PackageProjectUrl);
- iconUrl=$(PackageIconUrl);
- repositoryUrl=$(RepositoryUrl);
- repositoryCommit=$(RepositoryCommit);
- copyright=$(Copyright);
-
-
- ToolFiles=$(OutputPath)tools\**\*;
-
-
-
-
-
-
-
-
-
- <_RazorTool Include="$(OutputPath)tools\**\*" />
-
-
-
-
-
-
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.nuspec b/src/Razor/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.nuspec
deleted file mode 100644
index 47d16cd554..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/Microsoft.AspNetCore.Razor.Design.nuspec
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- $id$
- $version$
- $authors$
- $description$
- true
- $tags$
- $licenseUrl$
- $projectUrl$
- $iconUrl$
-
- $copyright$
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/baseline.netcore.json b/src/Razor/src/Microsoft.AspNetCore.Razor.Design/baseline.netcore.json
deleted file mode 100644
index 4491a03c76..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/baseline.netcore.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "AssemblyIdentity": "Microsoft.AspNetCore.Razor.Design, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
- "Types": []
-}
\ No newline at end of file
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/baseline.netframework.json b/src/Razor/src/Microsoft.AspNetCore.Razor.Design/baseline.netframework.json
deleted file mode 100644
index 7a73a41bfd..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/baseline.netframework.json
+++ /dev/null
@@ -1,2 +0,0 @@
-{
-}
\ No newline at end of file
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props b/src/Razor/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props
deleted file mode 100644
index 81b2e3afe4..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
- true
-
-
- $(MSBuildThisFileDirectory)Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets
-
-
- 2.1
-
-
-
-
- <_RazorMSBuildRoot Condition="'$(_RazorMSBuildRoot)'==''">$(MSBuildThisFileDirectory)..\..\
-
-
- <_RazorToolAssembly Condition="'$(_RazorToolAssembly)'==''">$(_RazorMSBuildRoot)tools\rzc.dll
-
-
-
-
-
-
-
-
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.props b/src/Razor/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.props
deleted file mode 100644
index 227f58dea9..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Design/buildMultiTargeting/Microsoft.AspNetCore.Razor.Design.props
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/CSharpIdentifier.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/CSharpIdentifier.cs
new file mode 100644
index 0000000000..1ec6188aed
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/CSharpIdentifier.cs
@@ -0,0 +1,53 @@
+// 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.Globalization;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Razor.Language
+{
+ internal static class CSharpIdentifier
+ {
+ // 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 SanitizeIdentifier(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();
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/ClassifiedSpanVisitor.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/ClassifiedSpanVisitor.cs
new file mode 100644
index 0000000000..ec0e7977d0
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/ClassifiedSpanVisitor.cs
@@ -0,0 +1,312 @@
+// 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.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
+
+namespace Microsoft.AspNetCore.Razor.Language
+{
+ internal class ClassifiedSpanVisitor : SyntaxWalker
+ {
+ private RazorSourceDocument _source;
+ private List _spans;
+ private BlockKindInternal _currentBlockKind;
+ private SyntaxNode _currentBlock;
+
+ public ClassifiedSpanVisitor(RazorSourceDocument source)
+ {
+ _source = source;
+ _spans = new List();
+ _currentBlockKind = BlockKindInternal.Markup;
+ }
+
+ public IReadOnlyList ClassifiedSpans => _spans;
+
+ public override void VisitRazorCommentBlock(RazorCommentBlockSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Comment, razorCommentSyntax =>
+ {
+ WriteSpan(razorCommentSyntax.StartCommentTransition, SpanKindInternal.Transition, AcceptedCharactersInternal.None);
+ WriteSpan(razorCommentSyntax.StartCommentStar, SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
+
+ var comment = razorCommentSyntax.Comment;
+ if (comment.IsMissing)
+ {
+ // We need to generate a classified span at this position. So insert a marker in its place.
+ comment = (SyntaxToken)SyntaxFactory.Token(SyntaxKind.Marker, string.Empty).Green.CreateRed(razorCommentSyntax, razorCommentSyntax.StartCommentStar.EndPosition);
+ }
+ WriteSpan(comment, SpanKindInternal.Comment, AcceptedCharactersInternal.Any);
+
+ WriteSpan(razorCommentSyntax.EndCommentStar, SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
+ WriteSpan(razorCommentSyntax.EndCommentTransition, SpanKindInternal.Transition, AcceptedCharactersInternal.None);
+ });
+ }
+
+ public override void VisitCSharpCodeBlock(CSharpCodeBlockSyntax node)
+ {
+ if (node.Parent is CSharpStatementBodySyntax ||
+ node.Parent is CSharpExplicitExpressionBodySyntax ||
+ node.Parent is CSharpImplicitExpressionBodySyntax ||
+ node.Parent is RazorDirectiveBodySyntax ||
+ (_currentBlockKind == BlockKindInternal.Directive &&
+ node.Children.Count == 1 &&
+ node.Children[0] is CSharpStatementLiteralSyntax))
+ {
+ base.VisitCSharpCodeBlock(node);
+ return;
+ }
+
+ WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpCodeBlock);
+ }
+
+ public override void VisitCSharpStatement(CSharpStatementSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Statement, base.VisitCSharpStatement);
+ }
+
+ public override void VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpExplicitExpression);
+ }
+
+ public override void VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Expression, base.VisitCSharpImplicitExpression);
+ }
+
+ public override void VisitRazorDirective(RazorDirectiveSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Directive, base.VisitRazorDirective);
+ }
+
+ public override void VisitCSharpTemplateBlock(CSharpTemplateBlockSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Template, base.VisitCSharpTemplateBlock);
+ }
+
+ public override void VisitMarkupBlock(MarkupBlockSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupBlock);
+ }
+
+ public override void VisitMarkupTagHelperAttributeValue(MarkupTagHelperAttributeValueSyntax node)
+ {
+ // We don't generate a classified span when the attribute value is a simple literal value.
+ // This is done so we maintain the classified spans generated in 2.x which
+ // used ConditionalAttributeCollapser (combines markup literal attribute values into one span with no block parent).
+ if (node.Children.Count > 1 ||
+ (node.Children.Count == 1 && node.Children[0] is MarkupDynamicAttributeValueSyntax))
+ {
+ WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupTagHelperAttributeValue);
+ return;
+ }
+
+ base.VisitMarkupTagHelperAttributeValue(node);
+ }
+
+ public override void VisitMarkupTagBlock(MarkupTagBlockSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupTagBlock);
+ }
+
+ public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupTagHelperElement);
+ }
+
+ public override void VisitMarkupTagHelperStartTag(MarkupTagHelperStartTagSyntax node)
+ {
+ foreach (var child in node.Children)
+ {
+ if (child is MarkupTagHelperAttributeSyntax attribute)
+ {
+ Visit(attribute);
+ }
+ }
+ }
+
+ public override void VisitMarkupTagHelperEndTag(MarkupTagHelperEndTagSyntax node)
+ {
+ // We don't want to generate a classified span for a tag helper end tag. Do nothing.
+ }
+
+ public override void VisitMarkupAttributeBlock(MarkupAttributeBlockSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Markup, n =>
+ {
+ var equalsSyntax = SyntaxFactory.MarkupTextLiteral(new SyntaxList(node.EqualsToken));
+ var mergedAttributePrefix = MergeTextLiteralSpans(node.NamePrefix, node.Name, node.NameSuffix, equalsSyntax, node.ValuePrefix);
+ Visit(mergedAttributePrefix);
+ Visit(node.Value);
+ Visit(node.ValueSuffix);
+ });
+ }
+
+ public override void VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
+ {
+ Visit(node.Value);
+ }
+
+ public override void VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Markup, n =>
+ {
+ var mergedAttributePrefix = MergeTextLiteralSpans(node.NamePrefix, node.Name);
+ Visit(mergedAttributePrefix);
+ });
+ }
+
+ public override void VisitMarkupCommentBlock(MarkupCommentBlockSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.HtmlComment, base.VisitMarkupCommentBlock);
+ }
+
+ public override void VisitMarkupDynamicAttributeValue(MarkupDynamicAttributeValueSyntax node)
+ {
+ WriteBlock(node, BlockKindInternal.Markup, base.VisitMarkupDynamicAttributeValue);
+ }
+
+ public override void VisitRazorMetaCode(RazorMetaCodeSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.MetaCode);
+ base.VisitRazorMetaCode(node);
+ }
+
+ public override void VisitCSharpTransition(CSharpTransitionSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Transition);
+ base.VisitCSharpTransition(node);
+ }
+
+ public override void VisitMarkupTransition(MarkupTransitionSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Transition);
+ base.VisitMarkupTransition(node);
+ }
+
+ public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Code);
+ base.VisitCSharpStatementLiteral(node);
+ }
+
+ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Code);
+ base.VisitCSharpExpressionLiteral(node);
+ }
+
+ public override void VisitCSharpEphemeralTextLiteral(CSharpEphemeralTextLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Code);
+ base.VisitCSharpEphemeralTextLiteral(node);
+ }
+
+ public override void VisitUnclassifiedTextLiteral(UnclassifiedTextLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.None);
+ base.VisitUnclassifiedTextLiteral(node);
+ }
+
+ public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValueSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Markup);
+ base.VisitMarkupLiteralAttributeValue(node);
+ }
+
+ public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node)
+ {
+ if (node.Parent is MarkupLiteralAttributeValueSyntax)
+ {
+ base.VisitMarkupTextLiteral(node);
+ return;
+ }
+
+ WriteSpan(node, SpanKindInternal.Markup);
+ base.VisitMarkupTextLiteral(node);
+ }
+
+ public override void VisitMarkupEphemeralTextLiteral(MarkupEphemeralTextLiteralSyntax node)
+ {
+ WriteSpan(node, SpanKindInternal.Markup);
+ base.VisitMarkupEphemeralTextLiteral(node);
+ }
+
+ private void WriteBlock(TNode node, BlockKindInternal kind, Action handler) where TNode : SyntaxNode
+ {
+ var previousBlock = _currentBlock;
+ var previousKind = _currentBlockKind;
+
+ _currentBlock = node;
+ _currentBlockKind = kind;
+
+ handler(node);
+
+ _currentBlock = previousBlock;
+ _currentBlockKind = previousKind;
+ }
+
+ private void WriteSpan(SyntaxNode node, SpanKindInternal kind, AcceptedCharactersInternal? acceptedCharacters = null)
+ {
+ if (node.IsMissing)
+ {
+ return;
+ }
+
+ var spanSource = node.GetSourceSpan(_source);
+ var blockSource = _currentBlock.GetSourceSpan(_source);
+ if (!acceptedCharacters.HasValue)
+ {
+ acceptedCharacters = AcceptedCharactersInternal.Any;
+ var context = node.GetSpanContext();
+ if (context != null)
+ {
+ acceptedCharacters = context.EditHandler.AcceptedCharacters;
+ }
+ }
+
+ var span = new ClassifiedSpanInternal(spanSource, blockSource, kind, _currentBlockKind, acceptedCharacters.Value);
+ _spans.Add(span);
+ }
+
+ private MarkupTextLiteralSyntax MergeTextLiteralSpans(params MarkupTextLiteralSyntax[] literalSyntaxes)
+ {
+ if (literalSyntaxes == null || literalSyntaxes.Length == 0)
+ {
+ return null;
+ }
+
+ SyntaxNode parent = null;
+ var position = 0;
+ var seenFirstLiteral = false;
+ var builder = Syntax.InternalSyntax.SyntaxListBuilder.Create();
+
+ foreach (var syntax in literalSyntaxes)
+ {
+ if (syntax == null)
+ {
+ continue;
+ }
+ else if (!seenFirstLiteral)
+ {
+ // Set the parent and position of the merged literal to the value of the first non-null literal.
+ parent = syntax.Parent;
+ position = syntax.Position;
+ seenFirstLiteral = true;
+ }
+
+ foreach (var token in syntax.LiteralTokens)
+ {
+ builder.Add(token.Green);
+ }
+ }
+
+ var mergedLiteralSyntax = Syntax.InternalSyntax.SyntaxFactory.MarkupTextLiteral(
+ builder.ToList());
+
+ return (MarkupTextLiteralSyntax)mergedLiteralSyntax.CreateRed(parent, position);
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/CodeGenerationConstants.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/CodeGenerationConstants.cs
new file mode 100644
index 0000000000..35c176a678
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/CodeGenerationConstants.cs
@@ -0,0 +1,27 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Razor.Language.Components
+{
+ // Constants for method names used in code-generation
+ // Keep these in sync with the actual definitions
+ internal static class CodeGenerationConstants
+ {
+ public static class RazorComponent
+ {
+ public const string FullTypeName = "Microsoft.AspNetCore.Components.Component";
+ public const string BuildRenderTree = "BuildRenderTree";
+ public const string BuildRenderTreeParameter = "builder";
+ }
+
+ public static class RenderTreeBuilder
+ {
+ public const string FullTypeName = "Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder";
+ }
+
+ public static class InjectDirective
+ {
+ public const string FullTypeName = "Microsoft.AspNetCore.Razor.Components.InjectAttribute";
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/ComponentDocumentClassifierPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/ComponentDocumentClassifierPass.cs
new file mode 100644
index 0000000000..29afa982af
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/ComponentDocumentClassifierPass.cs
@@ -0,0 +1,139 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Razor.Language.Components
+{
+ internal class ComponentDocumentClassifierPass : DocumentClassifierPassBase
+ {
+ public static readonly string ComponentDocumentKind = "component.1.0";
+ private static readonly object BuildRenderTreeBaseCallAnnotation = new object();
+ private static readonly char[] PathSeparators = new char[] { '/', '\\' };
+ private static readonly char[] NamespaceSeparators = new char[] { '.' };
+
+ protected override string DocumentKind => ComponentDocumentKind;
+
+ // Ensure this runs before the MVC classifiers which have Order = 0
+ public override int Order => -100;
+
+ protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ return codeDocument.GetInputDocumentKind() == InputDocumentKind.Component;
+ }
+
+ protected override void OnDocumentStructureCreated(RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method)
+ {
+ base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
+
+ if (!TryComputeNamespaceAndClass(
+ codeDocument.Source.FilePath,
+ codeDocument.Source.RelativePath,
+ out var computedNamespace,
+ out var computedClass))
+ {
+ // If we can't compute a nice namespace (no relative path) then just generate something
+ // mangled.
+ computedNamespace = "AspNetCore";
+ var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
+ computedClass = $"AspNetCore_{checksum}";
+ }
+
+ @namespace.Content = computedNamespace;
+
+ @class.ClassName = computedClass;
+ @class.BaseType = $"global::{CodeGenerationConstants.RazorComponent.FullTypeName}";
+ var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
+ if (string.IsNullOrEmpty(filePath))
+ {
+ // It's possible for a Razor document to not have a file path.
+ // Eg. When we try to generate code for an in memory document like default imports.
+ var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
+ @class.ClassName = $"AspNetCore_{checksum}";
+ }
+ else
+ {
+ @class.ClassName = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(filePath));
+ }
+
+ @class.Modifiers.Clear();
+ @class.Modifiers.Add("public");
+ @class.Modifiers.Add("sealed");
+
+ method.MethodName = CodeGenerationConstants.RazorComponent.BuildRenderTree;
+ method.ReturnType = "void";
+ method.Modifiers.Clear();
+ method.Modifiers.Add("public");
+ method.Modifiers.Add("override");
+
+ method.Parameters.Clear();
+ method.Parameters.Add(new MethodParameter()
+ {
+ TypeName = CodeGenerationConstants.RenderTreeBuilder.FullTypeName,
+ ParameterName = CodeGenerationConstants.RazorComponent.BuildRenderTreeParameter,
+ });
+
+ // We need to call the 'base' method as the first statement.
+ var callBase = new CSharpCodeIntermediateNode();
+ callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true);
+ callBase.Children.Add(new IntermediateToken
+ {
+ Kind = TokenKind.CSharp,
+ Content = $"base.{CodeGenerationConstants.RazorComponent.BuildRenderTree}({CodeGenerationConstants.RazorComponent.BuildRenderTreeParameter});"
+ });
+ method.Children.Insert(0, callBase);
+ }
+
+ private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class)
+ {
+ if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length)
+ {
+ @namespace = null;
+ @class = null;
+ return false;
+ }
+
+ // Try and infer a namespace from the project directory. We don't yet have the ability to pass
+ // the namespace through from the project.
+ var trimLength = relativePath.Length + (relativePath.StartsWith("/") ? 0 : 1);
+ var baseDirectory = filePath.Substring(0, filePath.Length - trimLength);
+
+ var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators);
+ var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1);
+ if (string.IsNullOrEmpty(baseNamespace))
+ {
+ @namespace = null;
+ @class = null;
+ return false;
+ }
+
+ var builder = new StringBuilder();
+
+ // Sanitize the base namespace, but leave the dots.
+ var segments = baseNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
+ builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0]));
+ for (var i = 1; i < segments.Length; i++)
+ {
+ builder.Append('.');
+ builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
+ }
+
+ segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries);
+
+ // Skip the last segment because it's the FileName.
+ for (var i = 0; i < segments.Length - 1; i++)
+ {
+ builder.Append('.');
+ builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
+ }
+
+ @namespace = builder.ToString();
+ @class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath));
+
+ return true;
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/ComponentExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/ComponentExtensions.cs
new file mode 100644
index 0000000000..6f7a2affa4
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Components/ComponentExtensions.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language.Extensions;
+
+namespace Microsoft.AspNetCore.Razor.Language.Components
+{
+ public static class ComponentExtensions
+ {
+ public static void Register(RazorProjectEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ FunctionsDirective.Register(builder);
+ builder.Features.Add(new ComponentDocumentClassifierPass());
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs
index 54c733e0f1..65e578b5ce 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultDirectiveSyntaxTreePass.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
@@ -23,37 +24,42 @@ namespace Microsoft.AspNetCore.Razor.Language
{
throw new ArgumentNullException(nameof(syntaxTree));
}
-
- var sectionVerifier = new NestedSectionVerifier();
- sectionVerifier.Verify(syntaxTree);
-
- return syntaxTree;
+
+ var sectionVerifier = new NestedSectionVerifier(syntaxTree);
+ return sectionVerifier.Verify();
}
- private class NestedSectionVerifier : ParserVisitor
+ private class NestedSectionVerifier : SyntaxRewriter
{
private int _nestedLevel;
+ private RazorSyntaxTree _syntaxTree;
- public void Verify(RazorSyntaxTree tree)
+ public NestedSectionVerifier(RazorSyntaxTree syntaxTree)
{
- tree.Root.Accept(this);
+ _syntaxTree = syntaxTree;
}
- public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
+ public RazorSyntaxTree Verify()
+ {
+ var root = Visit(_syntaxTree.Root);
+ var rewrittenTree = new DefaultRazorSyntaxTree(root, _syntaxTree.Source, _syntaxTree.Diagnostics, _syntaxTree.Options);
+ return rewrittenTree;
+ }
+
+ public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
{
if (_nestedLevel > 0)
{
- var directiveStart = block.Children.First(child => !child.IsBlock && ((Span)child).Kind == SpanKindInternal.Transition).Start;
+ var directiveStart = node.Transition.GetSourceLocation(_syntaxTree.Source);
var errorLength = /* @ */ 1 + SectionDirective.Directive.Directive.Length;
var error = RazorDiagnosticFactory.CreateParsing_SectionsCannotBeNested(new SourceSpan(directiveStart, errorLength));
- chunkGenerator.Diagnostics.Add(error);
+ node = node.AppendDiagnostic(error);
}
-
_nestedLevel++;
-
- VisitDefault(block);
-
+ var result = base.VisitRazorDirective(node);
_nestedLevel--;
+
+ return result;
}
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptions.cs
index e521141628..6b694d9382 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptions.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptions.cs
@@ -10,13 +10,15 @@ namespace Microsoft.AspNetCore.Razor.Language
int indentSize,
bool designTime,
bool suppressChecksum,
- bool supressMetadataAttributes)
+ bool supressMetadataAttributes,
+ bool suppressPrimaryMethodBody)
{
IndentWithTabs = indentWithTabs;
IndentSize = indentSize;
DesignTime = designTime;
SuppressChecksum = suppressChecksum;
SuppressMetadataAttributes = supressMetadataAttributes;
+ SuppressPrimaryMethodBody = suppressPrimaryMethodBody;
}
public override bool DesignTime { get; }
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs
index 8dd095a04e..7507ac0fee 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCodeGenerationOptionsBuilder.cs
@@ -31,10 +31,16 @@ namespace Microsoft.AspNetCore.Razor.Language
public override bool IndentWithTabs { get; set; }
public override bool SuppressChecksum { get; set; }
-
+
public override RazorCodeGenerationOptions Build()
{
- return new DefaultRazorCodeGenerationOptions(IndentWithTabs, IndentSize, DesignTime, SuppressChecksum, SuppressMetadataAttributes);
+ return new DefaultRazorCodeGenerationOptions(
+ IndentWithTabs,
+ IndentSize,
+ DesignTime,
+ SuppressChecksum,
+ SuppressMetadataAttributes,
+ SuppressPrimaryMethodBody);
}
public override void SetDesignTime(bool designTime)
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs
index a711567547..07e1ad44c8 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs
@@ -8,6 +8,7 @@ using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
@@ -46,8 +47,8 @@ namespace Microsoft.AspNetCore.Razor.Language
{
var import = imports[j];
- importsVisitor.FilePath = import.Source.FilePath;
- importsVisitor.VisitBlock(import.Root);
+ importsVisitor.SourceDocument = import.Source;
+ importsVisitor.Visit(import.Root);
}
importedUsings = importsVisitor.Usings;
@@ -56,10 +57,10 @@ namespace Microsoft.AspNetCore.Razor.Language
var tagHelperPrefix = tagHelperContext?.Prefix;
var visitor = new MainSourceVisitor(document, builder, tagHelperPrefix, syntaxTree.Options.FeatureFlags)
{
- FilePath = syntaxTree.Source.FilePath,
+ SourceDocument = syntaxTree.Source,
};
- visitor.VisitBlock(syntaxTree.Root);
+ visitor.Visit(syntaxTree.Root);
// 1. Prioritize non-imported usings over imported ones.
// 2. Don't import usings that already exist in primary document.
@@ -189,7 +190,7 @@ namespace Microsoft.AspNetCore.Razor.Language
public override int GetHashCode() => Namespace.GetHashCode();
}
- private class LoweringVisitor : ParserVisitor
+ private class LoweringVisitor : SyntaxWalker
{
protected readonly IntermediateNodeBuilder _builder;
protected readonly DocumentIntermediateNode _document;
@@ -206,195 +207,208 @@ namespace Microsoft.AspNetCore.Razor.Language
public IReadOnlyList Usings => _usings;
- public string FilePath { get; set; }
+ public RazorSourceDocument SourceDocument { get; set; }
- public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span)
- {
- _builder.Add(new DirectiveTokenIntermediateNode()
- {
- Content = span.Content,
- DirectiveToken = chunkGenerator.Descriptor,
- Source = BuildSourceSpanFromNode(span),
- });
- }
-
- public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block)
+ public override void VisitRazorDirective(RazorDirectiveSyntax node)
{
IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
+ var descriptor = node.DirectiveDescriptor;
+
+ if (descriptor != null)
{
- directiveNode = new MalformedDirectiveIntermediateNode()
+ var diagnostics = node.GetDiagnostics();
+
+ // This is an extensible directive.
+ if (IsMalformed(diagnostics))
{
- DirectiveName = chunkGenerator.Descriptor.Directive,
- Directive = chunkGenerator.Descriptor,
- Source = BuildSourceSpanFromNode(block),
- };
- }
- else
- {
- directiveNode = new DirectiveIntermediateNode()
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = descriptor.Directive,
+ Directive = descriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
{
- DirectiveName = chunkGenerator.Descriptor.Directive,
- Directive = chunkGenerator.Descriptor,
- Source = BuildSourceSpanFromNode(block),
- };
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = descriptor.Directive,
+ Directive = descriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+
+ for (var i = 0; i < diagnostics.Length; i++)
+ {
+ directiveNode.Diagnostics.Add(diagnostics[i]);
+ }
+
+ _builder.Push(directiveNode);
}
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
+ Visit(node.Body);
+
+ if (descriptor != null)
{
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
+ _builder.Pop();
}
-
- _builder.Push(directiveNode);
-
- VisitDefault(block);
-
- _builder.Pop();
}
- public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span)
+ public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
{
- var namespaceImport = chunkGenerator.Namespace.Trim();
- var namespaceSpan = BuildSourceSpanFromNode(span);
- _usings.Add(new UsingReference(namespaceImport, namespaceSpan));
+ var context = node.GetSpanContext();
+ if (context == null)
+ {
+ base.VisitCSharpStatementLiteral(node);
+ return;
+ }
+ else if (context.ChunkGenerator is DirectiveTokenChunkGenerator tokenChunkGenerator)
+ {
+ _builder.Add(new DirectiveTokenIntermediateNode()
+ {
+ Content = node.GetContent(),
+ DirectiveToken = tokenChunkGenerator.Descriptor,
+ Source = BuildSourceSpanFromNode(node),
+ });
+ }
+ else if (context.ChunkGenerator is AddImportChunkGenerator importChunkGenerator)
+ {
+ var namespaceImport = importChunkGenerator.Namespace.Trim();
+ var namespaceSpan = BuildSourceSpanFromNode(node);
+ _usings.Add(new UsingReference(namespaceImport, namespaceSpan));
+ }
+ else if (context.ChunkGenerator is AddTagHelperChunkGenerator addTagHelperChunkGenerator)
+ {
+ IntermediateNode directiveNode;
+ if (IsMalformed(addTagHelperChunkGenerator.Diagnostics))
+ {
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
+ {
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+
+ for (var i = 0; i < addTagHelperChunkGenerator.Diagnostics.Count; i++)
+ {
+ directiveNode.Diagnostics.Add(addTagHelperChunkGenerator.Diagnostics[i]);
+ }
+
+ _builder.Push(directiveNode);
+
+ _builder.Add(new DirectiveTokenIntermediateNode()
+ {
+ Content = addTagHelperChunkGenerator.LookupText,
+ DirectiveToken = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Tokens.First(),
+ Source = BuildSourceSpanFromNode(node),
+ });
+
+ _builder.Pop();
+ }
+ else if (context.ChunkGenerator is RemoveTagHelperChunkGenerator removeTagHelperChunkGenerator)
+ {
+ IntermediateNode directiveNode;
+ if (IsMalformed(removeTagHelperChunkGenerator.Diagnostics))
+ {
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
+ {
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+
+ for (var i = 0; i < removeTagHelperChunkGenerator.Diagnostics.Count; i++)
+ {
+ directiveNode.Diagnostics.Add(removeTagHelperChunkGenerator.Diagnostics[i]);
+ }
+
+ _builder.Push(directiveNode);
+
+ _builder.Add(new DirectiveTokenIntermediateNode()
+ {
+ Content = removeTagHelperChunkGenerator.LookupText,
+ DirectiveToken = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Tokens.First(),
+ Source = BuildSourceSpanFromNode(node),
+ });
+
+ _builder.Pop();
+ }
+ else if (context.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator tagHelperPrefixChunkGenerator)
+ {
+ IntermediateNode directiveNode;
+ if (IsMalformed(tagHelperPrefixChunkGenerator.Diagnostics))
+ {
+ directiveNode = new MalformedDirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+ else
+ {
+ directiveNode = new DirectiveIntermediateNode()
+ {
+ DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
+ Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
+ Source = BuildSourceSpanFromNode(node),
+ };
+ }
+
+ for (var i = 0; i < tagHelperPrefixChunkGenerator.Diagnostics.Count; i++)
+ {
+ directiveNode.Diagnostics.Add(tagHelperPrefixChunkGenerator.Diagnostics[i]);
+ }
+
+ _builder.Push(directiveNode);
+
+ _builder.Add(new DirectiveTokenIntermediateNode()
+ {
+ Content = tagHelperPrefixChunkGenerator.Prefix,
+ DirectiveToken = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Tokens.First(),
+ Source = BuildSourceSpanFromNode(node),
+ });
+
+ _builder.Pop();
+ }
+
+ base.VisitCSharpStatementLiteral(node);
}
- public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
+ protected SourceSpan? BuildSourceSpanFromNode(SyntaxNode node)
{
- IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
- {
- directiveNode = new MalformedDirectiveIntermediateNode()
- {
- DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
- else
- {
- directiveNode = new DirectiveIntermediateNode()
- {
- DirectiveName = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.AddTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
-
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
- {
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
- }
-
- _builder.Push(directiveNode);
-
- _builder.Add(new DirectiveTokenIntermediateNode()
- {
- Content = chunkGenerator.LookupText,
- DirectiveToken = CSharpCodeParser.AddTagHelperDirectiveDescriptor.Tokens.First(),
- Source = BuildSourceSpanFromNode(span),
- });
-
- _builder.Pop();
- }
-
- public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
- {
- IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
- {
- directiveNode = new MalformedDirectiveIntermediateNode()
- {
- DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
- else
- {
- directiveNode = new DirectiveIntermediateNode()
- {
- DirectiveName = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
-
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
- {
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
- }
-
- _builder.Push(directiveNode);
-
- _builder.Add(new DirectiveTokenIntermediateNode()
- {
- Content = chunkGenerator.LookupText,
- DirectiveToken = CSharpCodeParser.RemoveTagHelperDirectiveDescriptor.Tokens.First(),
- Source = BuildSourceSpanFromNode(span),
- });
-
- _builder.Pop();
- }
-
- public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
- {
- IntermediateNode directiveNode;
- if (IsMalformed(chunkGenerator.Diagnostics))
- {
- directiveNode = new MalformedDirectiveIntermediateNode()
- {
- DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
- else
- {
- directiveNode = new DirectiveIntermediateNode()
- {
- DirectiveName = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Directive,
- Directive = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
- Source = BuildSourceSpanFromNode(span),
- };
- }
-
- for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++)
- {
- directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]);
- }
-
- _builder.Push(directiveNode);
-
- _builder.Add(new DirectiveTokenIntermediateNode()
- {
- Content = chunkGenerator.Prefix,
- DirectiveToken = CSharpCodeParser.TagHelperPrefixDirectiveDescriptor.Tokens.First(),
- Source = BuildSourceSpanFromNode(span),
- });
-
- _builder.Pop();
- }
-
- protected SourceSpan? BuildSourceSpanFromNode(SyntaxTreeNode node)
- {
- if (node == null || node.Start == SourceLocation.Undefined)
+ if (node == null)
{
return null;
}
- var span = new SourceSpan(
- node.Start.FilePath ?? FilePath,
- node.Start.AbsoluteIndex,
- node.Start.LineIndex,
- node.Start.CharacterIndex,
- node.Length);
- return span;
+ return node.GetSourceSpan(SourceDocument);
}
}
private class MainSourceVisitor : LoweringVisitor
{
+ private readonly HashSet _renderedBoundAttributeNames = new HashSet(StringComparer.OrdinalIgnoreCase);
private readonly string _tagHelperPrefix;
public MainSourceVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, string tagHelperPrefix, RazorParserFeatureFlags featureFlags)
@@ -408,86 +422,144 @@ namespace Microsoft.AspNetCore.Razor.Language
// Name=checked
// Prefix= checked="
// Suffix="
- public override void VisitAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block)
+ public override void VisitMarkupAttributeBlock(MarkupAttributeBlockSyntax node)
{
- _builder.Push(new HtmlAttributeIntermediateNode()
+ var prefixTokens = MergeLiterals(
+ node.NamePrefix?.LiteralTokens,
+ node.Name.LiteralTokens,
+ node.NameSuffix?.LiteralTokens,
+ node.EqualsToken == null ? new SyntaxList() : new SyntaxList(node.EqualsToken),
+ node.ValuePrefix?.LiteralTokens);
+ var prefix = (MarkupTextLiteralSyntax)SyntaxFactory.MarkupTextLiteral(prefixTokens).Green.CreateRed(node, node.NamePrefix?.Position ?? node.Name.Position);
+
+ var name = node.Name.GetContent();
+ if (name.StartsWith("data-", StringComparison.OrdinalIgnoreCase) &&
+ !_featureFlags.EXPERIMENTAL_AllowConditionalDataDashAttributes)
{
- AttributeName = chunkGenerator.Name,
- Prefix = chunkGenerator.Prefix,
- Suffix = chunkGenerator.Suffix,
- Source = BuildSourceSpanFromNode(block),
- });
+ Visit(prefix);
+ Visit(node.Value);
+ Visit(node.ValueSuffix);
+ }
+ else
+ {
+ if (node.Value != null && node.Value.ChildNodes().All(c => c is MarkupLiteralAttributeValueSyntax))
+ {
+ // We need to do what ConditionalAttributeCollapser used to do.
+ var literalAttributeValueNodes = node.Value.ChildNodes().Cast().ToArray();
+ var valueTokens = SyntaxListBuilder.Create();
+ for (var i = 0; i < literalAttributeValueNodes.Length; i++)
+ {
+ var mergedValue = MergeAttributeValue(literalAttributeValueNodes[i]);
+ valueTokens.AddRange(mergedValue.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.MarkupTextLiteral(valueTokens.ToList());
- VisitDefault(block);
+ var mergedLiterals = MergeLiterals(prefix?.LiteralTokens, rewritten.LiteralTokens, node.ValueSuffix?.LiteralTokens);
+ var mergedAttribute = SyntaxFactory.MarkupTextLiteral(mergedLiterals).Green.CreateRed(node.Parent, node.Position);
+ Visit(mergedAttribute);
+ }
+ else
+ {
+ _builder.Push(new HtmlAttributeIntermediateNode()
+ {
+ AttributeName = node.Name.GetContent(),
+ Prefix = prefix.GetContent(),
+ Suffix = node.ValueSuffix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
+ });
- _builder.Pop();
+ VisitAttributeValue(node.Value);
+
+ _builder.Pop();
+ }
+ }
+ }
+
+ public override void VisitMarkupMinimizedAttributeBlock(MarkupMinimizedAttributeBlockSyntax node)
+ {
+ var name = node.Name.GetContent();
+ if (name.StartsWith("data-", StringComparison.OrdinalIgnoreCase) &&
+ !_featureFlags.EXPERIMENTAL_AllowConditionalDataDashAttributes)
+ {
+ base.VisitMarkupMinimizedAttributeBlock(node);
+ return;
+ }
+
+ // Minimized attributes are just html content.
+ var literals = MergeLiterals(
+ node.NamePrefix?.LiteralTokens,
+ node.Name?.LiteralTokens);
+ var literal = SyntaxFactory.MarkupTextLiteral(literals).Green.CreateRed(node.Parent, node.Position);
+
+ Visit(literal);
}
// Example
//
// Prefix= (space)
// Children will contain a token for @false.
- public override void VisitDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block)
+ public override void VisitMarkupDynamicAttributeValue(MarkupDynamicAttributeValueSyntax node)
{
- var firstChild = block.Children.FirstOrDefault(c => c.IsBlock) as Block;
- if (firstChild == null || firstChild.Type == BlockKindInternal.Expression)
+ var containsExpression = false;
+ var descendantNodes = node.DescendantNodes(n =>
+ {
+ // Don't go into sub block. They may contain expressions but we only care about the top level.
+ return !(n.Parent is CSharpCodeBlockSyntax);
+ });
+ foreach (var child in descendantNodes)
+ {
+ if (child is CSharpImplicitExpressionSyntax || child is CSharpExplicitExpressionSyntax)
+ {
+ containsExpression = true;
+ }
+ }
+
+ if (containsExpression)
{
_builder.Push(new CSharpExpressionAttributeValueIntermediateNode()
{
- Prefix = chunkGenerator.Prefix,
- Source = BuildSourceSpanFromNode(block),
+ Prefix = node.Prefix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
});
}
else
{
_builder.Push(new CSharpCodeAttributeValueIntermediateNode()
{
- Prefix = chunkGenerator.Prefix,
- Source = BuildSourceSpanFromNode(block),
+ Prefix = node.Prefix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
});
}
- VisitDefault(block);
+ Visit(node.Value);
_builder.Pop();
}
- public override void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunkGenerator, Span span)
+ public override void VisitMarkupLiteralAttributeValue(MarkupLiteralAttributeValueSyntax node)
{
_builder.Push(new HtmlAttributeValueIntermediateNode()
{
- Prefix = chunkGenerator.Prefix,
- Source = BuildSourceSpanFromNode(span),
+ Prefix = node.Prefix?.GetContent() ?? string.Empty,
+ Source = BuildSourceSpanFromNode(node),
});
- var location = chunkGenerator.Value.Location;
- SourceSpan? valueSpan = null;
- if (location != SourceLocation.Undefined)
- {
- valueSpan = new SourceSpan(
- location.FilePath ?? FilePath,
- location.AbsoluteIndex,
- location.LineIndex,
- location.CharacterIndex,
- chunkGenerator.Value.Value.Length);
- }
-
_builder.Add(new IntermediateToken()
{
- Content = chunkGenerator.Value,
+ Content = node.Value?.GetContent() ?? string.Empty,
Kind = TokenKind.Html,
- Source = valueSpan
+ Source = BuildSourceSpanFromNode(node.Value)
});
_builder.Pop();
}
- public override void VisitTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block)
+ public override void VisitCSharpTemplateBlock(CSharpTemplateBlockSyntax node)
{
var templateNode = new TemplateIntermediateNode();
_builder.Push(templateNode);
- VisitDefault(block);
+ base.VisitCSharpTemplateBlock(node);
_builder.Pop();
@@ -503,7 +575,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var contentLength = templateNode.Children.Sum(child => child.Source?.Length ?? 0);
templateNode.Source = new SourceSpan(
- sourceRangeStart.Value.FilePath ?? FilePath,
+ sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
sourceRangeStart.Value.AbsoluteIndex,
sourceRangeStart.Value.LineIndex,
sourceRangeStart.Value.CharacterIndex,
@@ -518,11 +590,11 @@ namespace Microsoft.AspNetCore.Razor.Language
// @DateTime.@*This is a comment*@Now
//
// We need to capture this in the IR so that we can give each piece the correct source mappings
- public override void VisitExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block)
+ public override void VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node)
{
if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode)
{
- VisitDefault(block);
+ base.VisitCSharpExplicitExpression(node);
return;
}
@@ -530,7 +602,7 @@ namespace Microsoft.AspNetCore.Razor.Language
_builder.Push(expressionNode);
- VisitDefault(block);
+ base.VisitCSharpExplicitExpression(node);
_builder.Pop();
@@ -546,7 +618,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0);
expressionNode.Source = new SourceSpan(
- sourceRangeStart.Value.FilePath ?? FilePath,
+ sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
sourceRangeStart.Value.AbsoluteIndex,
sourceRangeStart.Value.LineIndex,
sourceRangeStart.Value.CharacterIndex,
@@ -555,57 +627,120 @@ namespace Microsoft.AspNetCore.Razor.Language
}
}
- public override void VisitExpressionSpan(ExpressionChunkGenerator chunkGenerator, Span span)
+ public override void VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node)
{
- _builder.Add(new IntermediateToken()
+ if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode)
{
- Content = span.Content,
- Kind = TokenKind.CSharp,
- Source = BuildSourceSpanFromNode(span),
- });
- }
+ base.VisitCSharpImplicitExpression(node);
+ return;
+ }
- public override void VisitStatementSpan(StatementChunkGenerator chunkGenerator, Span span)
- {
- var isAttributeValue = _builder.Current is CSharpCodeAttributeValueIntermediateNode;
+ var expressionNode = new CSharpExpressionIntermediateNode();
- if (!isAttributeValue)
+ _builder.Push(expressionNode);
+
+ base.VisitCSharpImplicitExpression(node);
+
+ _builder.Pop();
+
+ if (expressionNode.Children.Count > 0)
{
- var statementNode = new CSharpCodeIntermediateNode()
+ var sourceRangeStart = expressionNode
+ .Children
+ .FirstOrDefault(child => child.Source != null)
+ ?.Source;
+
+ if (sourceRangeStart != null)
{
- Source = BuildSourceSpanFromNode(span)
- };
- _builder.Push(statementNode);
+ var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0);
+
+ expressionNode.Source = new SourceSpan(
+ sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath,
+ sourceRangeStart.Value.AbsoluteIndex,
+ sourceRangeStart.Value.LineIndex,
+ sourceRangeStart.Value.CharacterIndex,
+ contentLength);
+ }
+ }
+ }
+
+ public override void VisitCSharpExpressionLiteral(CSharpExpressionLiteralSyntax node)
+ {
+ if (_builder.Current is TagHelperHtmlAttributeIntermediateNode)
+ {
+ // If we are top level in a tag helper HTML attribute, we want to be rendered as markup.
+ // This case happens for duplicate non-string bound attributes. They would be initially be categorized as
+ // CSharp but since they are duplicate, they should just be markup.
+ var markupLiteral = SyntaxFactory.MarkupTextLiteral(node.LiteralTokens).Green.CreateRed(node.Parent, node.Position);
+ Visit(markupLiteral);
+ return;
}
_builder.Add(new IntermediateToken()
{
- Content = span.Content,
+ Content = node.GetContent(),
Kind = TokenKind.CSharp,
- Source = BuildSourceSpanFromNode(span),
+ Source = BuildSourceSpanFromNode(node),
});
- if (!isAttributeValue)
- {
- _builder.Pop();
- }
+ base.VisitCSharpExpressionLiteral(node);
}
- public override void VisitMarkupSpan(MarkupChunkGenerator chunkGenerator, Span span)
+ public override void VisitCSharpStatementLiteral(CSharpStatementLiteralSyntax node)
{
- if (span.Tokens.Count == 1)
+ var context = node.GetSpanContext();
+ if (context == null || context.ChunkGenerator is StatementChunkGenerator)
{
- var token = span.Tokens[0] as HtmlToken;
+ var isAttributeValue = _builder.Current is CSharpCodeAttributeValueIntermediateNode;
+
+ if (!isAttributeValue)
+ {
+ var statementNode = new CSharpCodeIntermediateNode()
+ {
+ Source = BuildSourceSpanFromNode(node)
+ };
+ _builder.Push(statementNode);
+ }
+
+ _builder.Add(new IntermediateToken()
+ {
+ Content = node.GetContent(),
+ Kind = TokenKind.CSharp,
+ Source = BuildSourceSpanFromNode(node),
+ });
+
+ if (!isAttributeValue)
+ {
+ _builder.Pop();
+ }
+ }
+
+ base.VisitCSharpStatementLiteral(node);
+ }
+
+ public override void VisitMarkupTextLiteral(MarkupTextLiteralSyntax node)
+ {
+ var context = node.GetSpanContext();
+ if (context != null && context.ChunkGenerator == SpanChunkGenerator.Null)
+ {
+ base.VisitMarkupTextLiteral(node);
+ return;
+ }
+
+ if (node.LiteralTokens.Count == 1)
+ {
+ var token = node.LiteralTokens[0];
if (token != null &&
- token.Type == HtmlTokenType.Unknown &&
+ token.Kind == SyntaxKind.Marker &&
token.Content.Length == 0)
{
// We don't want to create IR nodes for marker tokens.
+ base.VisitMarkupTextLiteral(node);
return;
}
}
- var source = BuildSourceSpanFromNode(span);
+ var source = BuildSourceSpanFromNode(node);
var currentChildren = _builder.Current.Children;
if (currentChildren.Count > 0 && currentChildren[currentChildren.Count - 1] is HtmlContentIntermediateNode)
{
@@ -613,7 +748,8 @@ namespace Microsoft.AspNetCore.Razor.Language
if (existingHtmlContent.Source == null && source == null)
{
- Combine(existingHtmlContent, span);
+ Combine(existingHtmlContent, node);
+ base.VisitMarkupTextLiteral(node);
return;
}
@@ -622,7 +758,8 @@ namespace Microsoft.AspNetCore.Razor.Language
existingHtmlContent.Source.Value.FilePath == source.Value.FilePath &&
existingHtmlContent.Source.Value.AbsoluteIndex + existingHtmlContent.Source.Value.Length == source.Value.AbsoluteIndex)
{
- Combine(existingHtmlContent, span);
+ Combine(existingHtmlContent, node);
+ base.VisitMarkupTextLiteral(node);
return;
}
}
@@ -635,23 +772,20 @@ namespace Microsoft.AspNetCore.Razor.Language
_builder.Add(new IntermediateToken()
{
- Content = span.Content,
+ Content = node.GetContent(),
Kind = TokenKind.Html,
Source = source,
});
_builder.Pop();
+
+ base.VisitMarkupTextLiteral(node);
}
- public override void VisitTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
+ public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node)
{
- var tagHelperBlock = block as TagHelperBlock;
- if (tagHelperBlock == null)
- {
- return;
- }
-
- var tagName = tagHelperBlock.TagName;
+ var info = node.TagHelperInfo;
+ var tagName = info.TagName;
if (_tagHelperPrefix != null)
{
tagName = tagName.Substring(_tagHelperPrefix.Length);
@@ -660,11 +794,11 @@ namespace Microsoft.AspNetCore.Razor.Language
var tagHelperNode = new TagHelperIntermediateNode()
{
TagName = tagName,
- TagMode = tagHelperBlock.TagMode,
- Source = BuildSourceSpanFromNode(block)
+ TagMode = info.TagMode,
+ Source = BuildSourceSpanFromNode(node)
};
- foreach (var tagHelper in tagHelperBlock.Binding.Descriptors)
+ foreach (var tagHelper in info.BindingResult.Descriptors)
{
tagHelperNode.TagHelpers.Add(tagHelper);
}
@@ -673,22 +807,223 @@ namespace Microsoft.AspNetCore.Razor.Language
_builder.Push(new TagHelperBodyIntermediateNode());
- VisitDefault(block);
+ foreach (var item in node.Body)
+ {
+ Visit(item);
+ }
_builder.Pop(); // Pop InitializeTagHelperStructureIntermediateNode
- AddTagHelperAttributes(tagHelperBlock.Attributes, tagHelperBlock.Binding);
+ Visit(node.StartTag);
_builder.Pop(); // Pop TagHelperIntermediateNode
+
+ // No need to visit the end tag because we don't write any IR for it.
+
+ // We don't want to track attributes from a previous tag helper element.
+ _renderedBoundAttributeNames.Clear();
}
- private void Combine(HtmlContentIntermediateNode node, Span span)
+ public override void VisitMarkupTagHelperStartTag(MarkupTagHelperStartTagSyntax node)
+ {
+ foreach (var child in node.Children)
+ {
+ if (child is MarkupTagHelperAttributeSyntax || child is MarkupMinimizedTagHelperAttributeSyntax)
+ {
+ Visit(child);
+ }
+ }
+ }
+
+ public override void VisitMarkupMinimizedTagHelperAttribute(MarkupMinimizedTagHelperAttributeSyntax node)
+ {
+ if (!_featureFlags.AllowMinimizedBooleanTagHelperAttributes)
+ {
+ // Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter
+ // has already logged an error if it was a non-boolean bound attribute; so we can skip.
+ return;
+ }
+
+ var element = node.FirstAncestorOrSelf();
+ var descriptors = element.TagHelperInfo.BindingResult.Descriptors;
+ var attributeName = node.Name.GetContent();
+ var associatedDescriptors = descriptors.Where(descriptor =>
+ descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));
+
+ if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
+ {
+ foreach (var associatedDescriptor in associatedDescriptors)
+ {
+ var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
+ {
+ return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a);
+ });
+
+ var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attributeName);
+
+ if (!expectsBooleanValue)
+ {
+ // We do not allow minimized non-boolean bound attributes.
+ return;
+ }
+
+ var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
+ {
+ AttributeName = attributeName,
+ BoundAttribute = associatedAttributeDescriptor,
+ TagHelper = associatedDescriptor,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
+ Source = null,
+ IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
+ };
+
+ _builder.Add(setTagHelperProperty);
+ }
+ }
+ else
+ {
+ var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
+ {
+ AttributeName = attributeName,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
+ };
+
+ _builder.Add(addHtmlAttribute);
+ }
+ }
+
+ public override void VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
+ {
+ var element = node.FirstAncestorOrSelf();
+ var descriptors = element.TagHelperInfo.BindingResult.Descriptors;
+ var attributeName = node.Name.GetContent();
+ var attributeValueNode = node.Value;
+ var associatedDescriptors = descriptors.Where(descriptor =>
+ descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));
+
+ if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
+ {
+ foreach (var associatedDescriptor in associatedDescriptors)
+ {
+ var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
+ {
+ return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a);
+ });
+
+ var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
+ {
+ AttributeName = attributeName,
+ BoundAttribute = associatedAttributeDescriptor,
+ TagHelper = associatedDescriptor,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
+ Source = BuildSourceSpanFromNode(attributeValueNode),
+ IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
+ };
+
+ _builder.Push(setTagHelperProperty);
+ VisitAttributeValue(attributeValueNode);
+ _builder.Pop();
+ }
+ }
+ else
+ {
+ var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
+ {
+ AttributeName = attributeName,
+ AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
+ };
+
+ _builder.Push(addHtmlAttribute);
+ VisitAttributeValue(attributeValueNode);
+ _builder.Pop();
+ }
+ }
+
+ private void VisitAttributeValue(SyntaxNode node)
+ {
+ if (node == null)
+ {
+ return;
+ }
+
+ IReadOnlyList children = node.ChildNodes();
+ var position = node.Position;
+ if (children.First() is MarkupBlockSyntax markupBlock &&
+ markupBlock.Children.Count == 2 &&
+ markupBlock.Children[0] is MarkupTextLiteralSyntax &&
+ markupBlock.Children[1] is MarkupEphemeralTextLiteralSyntax)
+ {
+ // This is a special case when we have an attribute like attr="@@foo".
+ // In this case, we want the foo to be written out as HtmlContent and not HtmlAttributeValue.
+ Visit(markupBlock);
+ children = children.Skip(1).ToList();
+ position = children.Count > 0 ? children[0].Position : position;
+ }
+
+ if (children.All(c => c is MarkupLiteralAttributeValueSyntax))
+ {
+ var literalAttributeValueNodes = children.Cast().ToArray();
+ var valueTokens = SyntaxListBuilder.Create();
+ for (var i = 0; i < literalAttributeValueNodes.Length; i++)
+ {
+ var mergedValue = MergeAttributeValue(literalAttributeValueNodes[i]);
+ valueTokens.AddRange(mergedValue.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.MarkupTextLiteral(valueTokens.ToList()).Green.CreateRed(node.Parent, position);
+ Visit(rewritten);
+ }
+ else if (children.All(c => c is MarkupTextLiteralSyntax))
+ {
+ var builder = SyntaxListBuilder.Create();
+ var markupLiteralArray = children.Cast();
+ foreach (var literal in markupLiteralArray)
+ {
+ builder.AddRange(literal.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.MarkupTextLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
+ Visit(rewritten);
+ }
+ else if (children.All(c => c is CSharpExpressionLiteralSyntax))
+ {
+ var builder = SyntaxListBuilder.Create();
+ var expressionLiteralArray = children.Cast();
+ SpanContext context = null;
+ foreach (var literal in expressionLiteralArray)
+ {
+ context = literal.GetSpanContext();
+ builder.AddRange(literal.LiteralTokens);
+ }
+ var rewritten = SyntaxFactory.CSharpExpressionLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
+ rewritten = context != null ? rewritten.WithSpanContext(context) : rewritten;
+ Visit(rewritten);
+ }
+ else
+ {
+ Visit(node);
+ }
+ }
+
+ private MarkupTextLiteralSyntax MergeAttributeValue(MarkupLiteralAttributeValueSyntax node)
+ {
+ var valueTokens = MergeLiterals(node.Prefix?.LiteralTokens, node.Value?.LiteralTokens);
+ var rewritten = node.Prefix?.Update(valueTokens) ?? node.Value?.Update(valueTokens);
+ rewritten = (MarkupTextLiteralSyntax)rewritten?.Green.CreateRed(node, node.Position);
+ var originalContext = rewritten.GetSpanContext();
+ if (originalContext != null)
+ {
+ rewritten = rewritten.WithSpanContext(new SpanContext(new MarkupChunkGenerator(), originalContext.EditHandler));
+ }
+
+ return rewritten;
+ }
+
+ private void Combine(HtmlContentIntermediateNode node, SyntaxNode item)
{
node.Children.Add(new IntermediateToken()
{
- Content = span.Content,
+ Content = item.GetContent(),
Kind = TokenKind.Html,
- Source = BuildSourceSpanFromNode(span),
+ Source = BuildSourceSpanFromNode(item),
});
if (node.Source != null)
@@ -700,76 +1035,25 @@ namespace Microsoft.AspNetCore.Razor.Language
node.Source.Value.AbsoluteIndex,
node.Source.Value.LineIndex,
node.Source.Value.CharacterIndex,
- node.Source.Value.Length + span.Content.Length);
+ node.Source.Value.Length + item.FullWidth);
}
}
- private void AddTagHelperAttributes(IList attributes, TagHelperBinding tagHelperBinding)
+ private SyntaxList MergeLiterals(params SyntaxList?[] literals)
{
- var descriptors = tagHelperBinding.Descriptors;
- var renderedBoundAttributeNames = new HashSet(StringComparer.OrdinalIgnoreCase);
- foreach (var attribute in attributes)
+ var builder = SyntaxListBuilder.Create();
+ for (var i = 0; i < literals.Length; i++)
{
- var attributeValueNode = attribute.Value;
- var associatedDescriptors = descriptors.Where(descriptor =>
- descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor)));
-
- if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name))
+ var literal = literals[i];
+ if (!literal.HasValue)
{
- var isMinimizedAttribute = attributeValueNode == null;
- if (isMinimizedAttribute && !_featureFlags.AllowMinimizedBooleanTagHelperAttributes)
- {
- // Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter
- // has already logged an error if it was a non-boolean bound attribute; so we can skip.
- continue;
- }
-
- foreach (var associatedDescriptor in associatedDescriptors)
- {
- var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
- {
- return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, a);
- });
-
- var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attribute.Name);
-
- if (isMinimizedAttribute && !expectsBooleanValue)
- {
- // We do not allow minimized non-boolean bound attributes.
- continue;
- }
-
- var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
- {
- AttributeName = attribute.Name,
- BoundAttribute = associatedAttributeDescriptor,
- TagHelper = associatedDescriptor,
- AttributeStructure = attribute.AttributeStructure,
- Source = BuildSourceSpanFromNode(attributeValueNode),
- IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attribute.Name, associatedAttributeDescriptor),
- };
-
- _builder.Push(setTagHelperProperty);
- attributeValueNode?.Accept(this);
- _builder.Pop();
- }
+ continue;
}
- else
- {
- var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
- {
- AttributeName = attribute.Name,
- AttributeStructure = attribute.AttributeStructure
- };
- _builder.Push(addHtmlAttribute);
- if (attributeValueNode != null)
- {
- attributeValueNode.Accept(this);
- }
- _builder.Pop();
- }
+ builder.AddRange(literal.Value);
}
+
+ return builder.ToList();
}
}
@@ -827,8 +1111,8 @@ namespace Microsoft.AspNetCore.Razor.Language
}
}
- private static bool IsMalformed(List diagnostics)
- => diagnostics.Count > 0 && diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
+ private static bool IsMalformed(IEnumerable diagnostics)
+ => diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
}
#pragma warning restore CS0618 // Type or member is obsolete
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs
index 2600ce5efe..9283088cd1 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs
@@ -49,6 +49,10 @@ namespace Microsoft.AspNetCore.Razor.Language
var absolutePath = NormalizeAndEnsureValidPath(path);
var file = new FileInfo(absolutePath);
+ if (!absolutePath.StartsWith(absoluteBasePath))
+ {
+ throw new InvalidOperationException($"The file '{file.FullName}' is not a descendent of the base path '{absoluteBasePath}'.");
+ }
var relativePhysicalPath = file.FullName.Substring(absoluteBasePath.Length + 1); // Include leading separator
var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/');
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs
index 4b6d43fe06..018dbfe5fb 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorSyntaxTree.cs
@@ -1,15 +1,17 @@
// 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.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorSyntaxTree : RazorSyntaxTree
{
public DefaultRazorSyntaxTree(
- Block root,
+ SyntaxNode root,
RazorSourceDocument source,
IReadOnlyList diagnostics,
RazorParserOptions options)
@@ -24,7 +26,7 @@ namespace Microsoft.AspNetCore.Razor.Language
public override RazorParserOptions Options { get; }
- internal override Block Root { get; }
+ internal override SyntaxNode Root { get; }
public override RazorSourceDocument Source { get; }
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
index aa0668a539..c15aaff04e 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
@@ -39,11 +40,11 @@ namespace Microsoft.AspNetCore.Razor.Language
for (var i = 0; i < imports.Count; i++)
{
var import = imports[i];
- visitor.VisitBlock(import.Root);
+ visitor.Visit(import.Root);
}
}
- visitor.VisitBlock(syntaxTree.Root);
+ visitor.Visit(syntaxTree.Root);
var tagHelperPrefix = visitor.TagHelperPrefix;
descriptors = visitor.Matches.ToArray();
@@ -57,21 +58,9 @@ namespace Microsoft.AspNetCore.Razor.Language
return;
}
- var errorSink = new ErrorSink();
- var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors, syntaxTree.Options.FeatureFlags);
-
- var root = syntaxTree.Root;
- root = rewriter.Rewrite(root, errorSink);
-
- var errorList = new List();
- errorList.AddRange(errorSink.Errors);
-
- errorList.AddRange(descriptors.SelectMany(d => d.GetAllDiagnostics()));
-
- var diagnostics = CombineErrors(syntaxTree.Diagnostics, errorList);
-
- var newSyntaxTree = RazorSyntaxTree.Create(root, syntaxTree.Source, diagnostics, syntaxTree.Options);
- codeDocument.SetSyntaxTree(newSyntaxTree);
+ var rewrittenSyntaxTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors);
+
+ codeDocument.SetSyntaxTree(rewrittenSyntaxTree);
}
private static bool MatchesDirective(TagHelperDescriptor descriptor, string typePattern, string assemblyName)
@@ -97,25 +86,7 @@ namespace Microsoft.AspNetCore.Razor.Language
return string.Equals(descriptor.Name, typePattern, StringComparison.Ordinal);
}
- private static int GetErrorLength(string directiveText)
- {
- var nonNullLength = directiveText == null ? 1 : directiveText.Length;
- var normalizeEmptyStringLength = Math.Max(nonNullLength, 1);
-
- return normalizeEmptyStringLength;
- }
-
- private IReadOnlyList CombineErrors(IReadOnlyList errors1, IReadOnlyList errors2)
- {
- var combinedErrors = new List(errors1.Count + errors2.Count);
- combinedErrors.AddRange(errors1);
- combinedErrors.AddRange(errors2);
-
- return combinedErrors;
- }
-
- // Internal for testing.
- internal class DirectiveVisitor : ParserVisitor
+ internal class DirectiveVisitor : SyntaxRewriter
{
private IReadOnlyList _tagHelpers;
@@ -128,62 +99,80 @@ namespace Microsoft.AspNetCore.Razor.Language
public HashSet Matches { get; } = new HashSet();
- public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
+ public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
{
- if (chunkGenerator.AssemblyName == null)
+ var descendantLiterals = node.DescendantNodes();
+ foreach (var child in descendantLiterals)
{
- // Skip this one, it's an error
- return;
- }
-
- if (!AssemblyContainsTagHelpers(chunkGenerator.AssemblyName, _tagHelpers))
- {
- // No tag helpers in the assembly.
- return;
- }
-
- for (var i = 0; i < _tagHelpers.Count; i++)
- {
- var tagHelper = _tagHelpers[i];
- if (MatchesDirective(tagHelper, chunkGenerator.TypePattern, chunkGenerator.AssemblyName))
+ if (!(child is CSharpStatementLiteralSyntax literal))
{
- Matches.Add(tagHelper);
+ continue;
+ }
+
+ var context = literal.GetSpanContext();
+ if (context == null)
+ {
+ // We can't find a chunk generator.
+ continue;
+ }
+ else if (context.ChunkGenerator is AddTagHelperChunkGenerator addTagHelper)
+ {
+ if (addTagHelper.AssemblyName == null)
+ {
+ // Skip this one, it's an error
+ continue;
+ }
+
+ if (!AssemblyContainsTagHelpers(addTagHelper.AssemblyName, _tagHelpers))
+ {
+ // No tag helpers in the assembly.
+ continue;
+ }
+
+ for (var i = 0; i < _tagHelpers.Count; i++)
+ {
+ var tagHelper = _tagHelpers[i];
+ if (MatchesDirective(tagHelper, addTagHelper.TypePattern, addTagHelper.AssemblyName))
+ {
+ Matches.Add(tagHelper);
+ }
+ }
+ }
+ else if (context.ChunkGenerator is RemoveTagHelperChunkGenerator removeTagHelper)
+ {
+ if (removeTagHelper.AssemblyName == null)
+ {
+ // Skip this one, it's an error
+ continue;
+ }
+
+
+ if (!AssemblyContainsTagHelpers(removeTagHelper.AssemblyName, _tagHelpers))
+ {
+ // No tag helpers in the assembly.
+ continue;
+ }
+
+ for (var i = 0; i < _tagHelpers.Count; i++)
+ {
+ var tagHelper = _tagHelpers[i];
+ if (MatchesDirective(tagHelper, removeTagHelper.TypePattern, removeTagHelper.AssemblyName))
+ {
+ Matches.Remove(tagHelper);
+ }
+ }
+ }
+ else if (context.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator tagHelperPrefix)
+ {
+ if (!string.IsNullOrEmpty(tagHelperPrefix.DirectiveText))
+ {
+ // We only expect to see a single one of these per file, but that's enforced at another level.
+ TagHelperPrefix = tagHelperPrefix.DirectiveText;
+ }
}
}
- }
- public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
- {
- if (chunkGenerator.AssemblyName == null)
- {
- // Skip this one, it's an error
- return;
- }
-
-
- if (!AssemblyContainsTagHelpers(chunkGenerator.AssemblyName, _tagHelpers))
- {
- // No tag helpers in the assembly.
- return;
- }
-
- for (var i = 0; i < _tagHelpers.Count; i++)
- {
- var tagHelper = _tagHelpers[i];
- if (MatchesDirective(tagHelper, chunkGenerator.TypePattern, chunkGenerator.AssemblyName))
- {
- Matches.Remove(tagHelper);
- }
- }
- }
-
- public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
- {
- if (!string.IsNullOrEmpty(chunkGenerator.DirectiveText))
- {
- // We only expect to see a single one of these per file, but that's enforced at another level.
- TagHelperPrefix = chunkGenerator.DirectiveText;
- }
+ return base.VisitRazorDirective(node);
}
private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList tagHelpers)
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenEditHandler.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenEditHandler.cs
index 308c274518..c84d8b0c67 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenEditHandler.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/DirectiveTokenEditHandler.cs
@@ -4,18 +4,19 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.Legacy;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DirectiveTokenEditHandler : SpanEditHandler
{
- public DirectiveTokenEditHandler(Func> tokenizer) : base(tokenizer)
+ public DirectiveTokenEditHandler(Func> tokenizer) : base(tokenizer)
{
}
- protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
+ protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
{
- if (AcceptedCharacters == AcceptedCharactersInternal.NonWhiteSpace)
+ if (AcceptedCharacters == AcceptedCharactersInternal.NonWhitespace)
{
var originalText = change.GetOriginalText(target);
var editedContent = change.GetEditedContent(target);
@@ -30,7 +31,6 @@ namespace Microsoft.AspNetCore.Razor.Language
}
return PartialParseResultInternal.Rejected;
-
}
private static bool ContainsWhitespace(string content)
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectiveTargetExtension.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectiveTargetExtension.cs
index 316c255bfe..29cc438fbb 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectiveTargetExtension.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Extensions/DesignTimeDirectiveTargetExtension.cs
@@ -65,15 +65,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
// {node.Content} __typeHelper = default({node.Content});
-
- context.AddSourceMappingFor(node);
- context.CodeWriter
- .Write(node.Content)
- .Write(" ")
- .WriteStartAssignment(TypeHelper)
- .Write("default(")
- .Write(node.Content)
- .WriteLine(");");
+ using (context.CodeWriter.BuildLinePragma(node.Source))
+ {
+ context.AddSourceMappingFor(node);
+ context.CodeWriter
+ .Write(node.Content)
+ .Write(" ")
+ .WriteStartAssignment(TypeHelper)
+ .Write("default(")
+ .Write(node.Content)
+ .WriteLine(");");
+ }
break;
case DirectiveTokenKind.Member:
@@ -86,16 +88,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
// global::System.Object {node.content} = null;
-
- context.CodeWriter
+ using (context.CodeWriter.BuildLinePragma(node.Source))
+ {
+ context.CodeWriter
.Write("global::")
.Write(typeof(object).FullName)
.Write(" ");
- context.AddSourceMappingFor(node);
- context.CodeWriter
- .Write(node.Content)
- .WriteLine(" = null;");
+ context.AddSourceMappingFor(node);
+ context.CodeWriter
+ .Write(node.Content)
+ .WriteLine(" = null;");
+ }
break;
case DirectiveTokenKind.Namespace:
@@ -108,46 +112,50 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
// global::System.Object __typeHelper = nameof({node.Content});
-
- context.CodeWriter
+ using (context.CodeWriter.BuildLinePragma(node.Source))
+ {
+ context.CodeWriter
.Write("global::")
.Write(typeof(object).FullName)
.Write(" ")
.WriteStartAssignment(TypeHelper);
- context.CodeWriter.Write("nameof(");
+ context.CodeWriter.Write("nameof(");
- context.AddSourceMappingFor(node);
- context.CodeWriter
- .Write(node.Content)
- .WriteLine(");");
+ context.AddSourceMappingFor(node);
+ context.CodeWriter
+ .Write(node.Content)
+ .WriteLine(");");
+ }
break;
case DirectiveTokenKind.String:
// global::System.Object __typeHelper = "{node.Content}";
-
- context.CodeWriter
+ using (context.CodeWriter.BuildLinePragma(node.Source))
+ {
+ context.CodeWriter
.Write("global::")
.Write(typeof(object).FullName)
.Write(" ")
.WriteStartAssignment(TypeHelper);
- if (node.Content.StartsWith("\"", StringComparison.Ordinal))
- {
- context.AddSourceMappingFor(node);
- context.CodeWriter.Write(node.Content);
- }
- else
- {
- context.CodeWriter.Write("\"");
- context.AddSourceMappingFor(node);
- context.CodeWriter
- .Write(node.Content)
- .Write("\"");
- }
+ if (node.Content.StartsWith("\"", StringComparison.Ordinal))
+ {
+ context.AddSourceMappingFor(node);
+ context.CodeWriter.Write(node.Content);
+ }
+ else
+ {
+ context.CodeWriter.Write("\"");
+ context.AddSourceMappingFor(node);
+ context.CodeWriter
+ .Write(node.Content)
+ .Write("\"");
+ }
- context.CodeWriter.WriteLine(";");
+ context.CodeWriter.WriteLine(";");
+ }
break;
}
context.CodeWriter.CurrentIndent = originalIndent;
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Extensions/EliminateMethodBodyPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Extensions/EliminateMethodBodyPass.cs
new file mode 100644
index 0000000000..96d336ca72
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Extensions/EliminateMethodBodyPass.cs
@@ -0,0 +1,71 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language.Intermediate;
+
+namespace Microsoft.AspNetCore.Razor.Language.Extensions
+{
+ internal sealed class EliminateMethodBodyPass : IntermediateNodePassBase, IRazorOptimizationPass
+ {
+ // Run early in the optimization phase
+ public override int Order => int.MinValue;
+
+ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
+ {
+ if (codeDocument == null)
+ {
+ throw new ArgumentNullException(nameof(codeDocument));
+ }
+
+ if (documentNode == null)
+ {
+ throw new ArgumentNullException(nameof(documentNode));
+ }
+
+ var codeGenerationOptions = codeDocument.GetCodeGenerationOptions();
+ if (codeGenerationOptions == null || !codeGenerationOptions.SuppressPrimaryMethodBody)
+ {
+ return;
+ }
+
+ var method = documentNode.FindPrimaryMethod();
+ if (method == null)
+ {
+ return;
+ }
+
+ method.Children.Clear();
+
+ // After we clear all of the method body there might be some unused fields, which can be
+ // blocking if compiling with warnings as errors. Suppress this warning so that it doesn't
+ // get annoying in VS.
+ documentNode.Children.Insert(documentNode.Children.IndexOf(documentNode.FindPrimaryNamespace()), new CSharpCodeIntermediateNode()
+ {
+ Children =
+ {
+ // Field is assigned but never used
+ new IntermediateToken()
+ {
+ Content = "#pragma warning disable 0414" + Environment.NewLine,
+ Kind = TokenKind.CSharp,
+ },
+
+ // Field is never assigned
+ new IntermediateToken()
+ {
+ Content = "#pragma warning disable 0649" + Environment.NewLine,
+ Kind = TokenKind.CSharp,
+ },
+
+ // Field is never used
+ new IntermediateToken()
+ {
+ Content = "#pragma warning disable 0169" + Environment.NewLine,
+ Kind = TokenKind.CSharp,
+ },
+ },
+ });
+ }
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs
index ea3d754554..1b327aa032 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/HtmlNodeOptimizationPass.cs
@@ -22,14 +22,11 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(syntaxTree));
}
- var conditionalAttributeCollapser = new ConditionalAttributeCollapser();
- var rewritten = conditionalAttributeCollapser.Rewrite(syntaxTree.Root);
-
- var whitespaceRewriter = new WhiteSpaceRewriter();
- rewritten = whitespaceRewriter.Rewrite(rewritten);
+ var whitespaceRewriter = new WhitespaceRewriter();
+ var rewritten = whitespaceRewriter.Visit(syntaxTree.Root);
var rewrittenSyntaxTree = RazorSyntaxTree.Create(rewritten, syntaxTree.Source, syntaxTree.Diagnostics, syntaxTree.Options);
return rewrittenSyntaxTree;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Intermediate/InputDocumentKind.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Intermediate/InputDocumentKind.cs
new file mode 100644
index 0000000000..7adf2bd8b5
--- /dev/null
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Intermediate/InputDocumentKind.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Razor.Language.Intermediate
+{
+ internal static class InputDocumentKind
+ {
+ public static readonly string Component = "component";
+
+ public static readonly string MvcFile = "mvc";
+ }
+}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AcceptedCharactersInternal.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AcceptedCharactersInternal.cs
index f6b658f769..9838a182b5 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AcceptedCharactersInternal.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AcceptedCharactersInternal.cs
@@ -10,13 +10,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
None = 0,
NewLine = 1,
- WhiteSpace = 2,
+ Whitespace = 2,
- NonWhiteSpace = 4,
+ NonWhitespace = 4,
- AllWhiteSpace = NewLine | WhiteSpace,
- Any = AllWhiteSpace | NonWhiteSpace,
+ AllWhitespace = NewLine | Whitespace,
+ Any = AllWhitespace | NonWhitespace,
- AnyExceptNewline = NonWhiteSpace | WhiteSpace
+ AnyExceptNewline = NonWhitespace | Whitespace
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddImportChunkGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddImportChunkGenerator.cs
index 0d7b9de155..4cee1abe3e 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddImportChunkGenerator.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddImportChunkGenerator.cs
@@ -14,23 +14,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public string Namespace { get; }
- public override void Accept(ParserVisitor visitor, Span span)
- {
- visitor.VisitImportSpan(this, span);
- }
-
- public override void GenerateChunk(Span target, ChunkGeneratorContext context)
- {
- var ns = Namespace;
-
- if (!string.IsNullOrEmpty(ns) && char.IsWhiteSpace(ns[0]))
- {
- ns = ns.Substring(1);
- }
-
- //context.ChunkTreeBuilder.AddUsingChunk(ns, target);
- }
-
public override string ToString()
{
return "Import:" + Namespace + ";";
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs
index f446be12c0..2df86b32de 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AddTagHelperChunkGenerator.cs
@@ -35,11 +35,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public List Diagnostics { get; }
- public override void Accept(ParserVisitor visitor, Span span)
- {
- visitor.VisitAddTagHelperSpan(this, span);
- }
-
///
public override bool Equals(object obj)
{
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AttributeBlockChunkGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AttributeBlockChunkGenerator.cs
deleted file mode 100644
index de77a1746d..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AttributeBlockChunkGenerator.cs
+++ /dev/null
@@ -1,68 +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 Microsoft.Extensions.Internal;
-
-namespace Microsoft.AspNetCore.Razor.Language.Legacy
-{
- internal class AttributeBlockChunkGenerator : ParentChunkGenerator
- {
- public AttributeBlockChunkGenerator(string name, LocationTagged prefix, LocationTagged suffix)
- {
- Name = name;
- Prefix = prefix;
- Suffix = suffix;
- }
-
- public string Name { get; }
-
- public LocationTagged Prefix { get; }
-
- public LocationTagged Suffix { get; }
-
- public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
- {
- //var chunk = context.ChunkTreeBuilder.StartParentChunk(target);
-
- //chunk.Attribute = Name;
- //chunk.Prefix = Prefix;
- //chunk.Suffix = Suffix;
- }
-
- public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
- {
- //context.ChunkTreeBuilder.EndParentChunk();
- }
-
- public override void Accept(ParserVisitor visitor, Block block)
- {
- visitor.VisitAttributeBlock(this, block);
- }
-
- public override string ToString()
- {
- return string.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix);
- }
-
- public override bool Equals(object obj)
- {
- var other = obj as AttributeBlockChunkGenerator;
- return other != null &&
- string.Equals(other.Name, Name, StringComparison.Ordinal) &&
- Equals(other.Prefix, Prefix) &&
- Equals(other.Suffix, Suffix);
- }
-
- public override int GetHashCode()
- {
- var hashCodeCombiner = HashCodeCombiner.Start();
- hashCodeCombiner.Add(Name, StringComparer.Ordinal);
- hashCodeCombiner.Add(Prefix);
- hashCodeCombiner.Add(Suffix);
-
- return hashCodeCombiner;
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AutoCompleteEditHandler.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AutoCompleteEditHandler.cs
index 3aecb29a52..2514377780 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AutoCompleteEditHandler.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/AutoCompleteEditHandler.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
@@ -11,18 +12,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
private static readonly int TypeHashCode = typeof(AutoCompleteEditHandler).GetHashCode();
- public AutoCompleteEditHandler(Func> tokenizer)
+ public AutoCompleteEditHandler(Func> tokenizer)
: base(tokenizer)
{
}
- public AutoCompleteEditHandler(Func> tokenizer, bool autoCompleteAtEndOfSpan)
+ public AutoCompleteEditHandler(Func> tokenizer, bool autoCompleteAtEndOfSpan)
: this(tokenizer)
{
AutoCompleteAtEndOfSpan = autoCompleteAtEndOfSpan;
}
- public AutoCompleteEditHandler(Func> tokenizer, AcceptedCharactersInternal accepted)
+ public AutoCompleteEditHandler(Func> tokenizer, AcceptedCharactersInternal accepted)
: base(tokenizer, accepted)
{
}
@@ -31,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public string AutoCompleteString { get; set; }
- protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
+ protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
{
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, change)) || IsAtEndOfFirstLine(target, change)) &&
change.IsInsert &&
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/Block.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/Block.cs
deleted file mode 100644
index ee7cc135f1..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/Block.cs
+++ /dev/null
@@ -1,288 +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.Globalization;
-using System.Linq;
-using Microsoft.Extensions.Internal;
-
-namespace Microsoft.AspNetCore.Razor.Language.Legacy
-{
- internal class Block : SyntaxTreeNode
- {
- private int? _length;
-
- public Block(BlockBuilder source)
- : this(source.Type, source.Children, source.ChunkGenerator)
- {
- source.Reset();
- }
-
- protected Block(BlockKindInternal? type, IReadOnlyList children, IParentChunkGenerator generator)
- {
- if (type == null)
- {
- throw new InvalidOperationException(Resources.Block_Type_Not_Specified);
- }
-
- Type = type.Value;
- Children = children;
- ChunkGenerator = generator;
-
- // Perf: Avoid allocating an enumerator.
- for (var i = 0; i < Children.Count; i++)
- {
- Children[i].Parent = this;
- }
- }
- public IParentChunkGenerator ChunkGenerator { get; }
-
- public BlockKindInternal Type { get; }
-
- public IReadOnlyList Children { get; }
-
- public override bool IsBlock => true;
-
- public override SourceLocation Start
- {
- get
- {
- var child = Children.FirstOrDefault();
- if (child == null)
- {
- return SourceLocation.Zero;
- }
- else
- {
- return child.Start;
- }
- }
- }
-
- public override int Length
- {
- get
- {
- if (_length == null)
- {
- var length = 0;
- for (var i = 0; i < Children.Count; i++)
- {
- length += Children[i].Length;
- }
-
- _length = length;
- }
-
- return _length.Value;
- }
- }
-
-
- public virtual IEnumerable Flatten()
- {
- // Perf: Avoid allocating an enumerator.
- for (var i = 0; i < Children.Count; i++)
- {
- var element = Children[i];
- var span = element as Span;
- if (span != null)
- {
- yield return span;
- }
- else
- {
- var block = element as Block;
- foreach (Span childSpan in block.Flatten())
- {
- yield return childSpan;
- }
- }
- }
- }
-
- public Span FindFirstDescendentSpan()
- {
- SyntaxTreeNode current = this;
- while (current != null && current.IsBlock)
- {
- current = ((Block)current).Children.FirstOrDefault();
- }
- return current as Span;
- }
-
- public Span FindLastDescendentSpan()
- {
- SyntaxTreeNode current = this;
- while (current != null && current.IsBlock)
- {
- current = ((Block)current).Children.LastOrDefault();
- }
- return current as Span;
- }
-
- public virtual Span LocateOwner(SourceChange change) => LocateOwner(change, Children);
-
- protected static Span LocateOwner(SourceChange change, IEnumerable elements)
- {
- // Ask each child recursively
- Span owner = null;
- foreach (var element in elements)
- {
- var span = element as Span;
- if (span == null)
- {
- owner = ((Block)element).LocateOwner(change);
- }
- else
- {
- if (change.Span.AbsoluteIndex < span.Start.AbsoluteIndex)
- {
- // Early escape for cases where changes overlap multiple spans
- // In those cases, the span will return false, and we don't want to search the whole tree
- // So if the current span starts after the change, we know we've searched as far as we need to
- break;
- }
- owner = span.EditHandler.OwnsChange(span, change) ? span : owner;
- }
-
- if (owner != null)
- {
- break;
- }
- }
- return owner;
- }
-
- public override string ToString()
- {
- return string.Format(
- CultureInfo.CurrentCulture,
- "{0} Block at {1}::{2} (Gen:{3})",
- Type,
- Start,
- Length,
- ChunkGenerator);
- }
-
- public override bool Equals(object obj)
- {
- var other = obj as Block;
- return other != null &&
- Type == other.Type &&
- Equals(ChunkGenerator, other.ChunkGenerator) &&
- ChildrenEqual(Children, other.Children);
- }
-
- public override int GetHashCode()
- {
- var hashCodeCombiner = HashCodeCombiner.Start();
- hashCodeCombiner.Add(Type);
- hashCodeCombiner.Add(ChunkGenerator);
- hashCodeCombiner.Add(Children);
-
- return hashCodeCombiner;
- }
-
- private static bool ChildrenEqual(IEnumerable left, IEnumerable right)
- {
- IEnumerator leftEnum = left.GetEnumerator();
- IEnumerator rightEnum = right.GetEnumerator();
- while (leftEnum.MoveNext())
- {
- if (!rightEnum.MoveNext() || // More items in left than in right
- !Equals(leftEnum.Current, rightEnum.Current))
- {
- // Nodes are not equal
- return false;
- }
- }
- if (rightEnum.MoveNext())
- {
- // More items in right than left
- return false;
- }
- return true;
- }
-
- public override bool EquivalentTo(SyntaxTreeNode node)
- {
- var other = node as Block;
- if (other == null || other.Type != Type)
- {
- return false;
- }
-
- return Enumerable.SequenceEqual(Children, other.Children, EquivalenceComparer.Default);
- }
-
- public override int GetEquivalenceHash()
- {
- var hashCodeCombiner = HashCodeCombiner.Start();
- hashCodeCombiner.Add(Type);
- foreach (var child in Children)
- {
- hashCodeCombiner.Add(child.GetEquivalenceHash());
- }
-
- return hashCodeCombiner.CombinedHash;
- }
-
- public override void Accept(ParserVisitor visitor)
- {
- visitor.VisitBlock(this);
- }
-
- public override SyntaxTreeNode Clone()
- {
- var blockBuilder = new BlockBuilder(this);
-
- blockBuilder.Children.Clear();
- for (var i = 0; i < Children.Count; i++)
- {
- var clonedChild = Children[i].Clone();
- blockBuilder.Children.Add(clonedChild);
- }
-
- return blockBuilder.Build();
- }
-
- internal void ChildChanged()
- {
- // A node in our graph has changed. We'll need to recompute our length the next time we're asked for it.
- _length = null;
-
- Parent?.ChildChanged();
- }
-
- private class EquivalenceComparer : IEqualityComparer
- {
- public static readonly EquivalenceComparer Default = new EquivalenceComparer();
-
- private EquivalenceComparer()
- {
- }
-
- public bool Equals(SyntaxTreeNode nodeX, SyntaxTreeNode nodeY)
- {
- if (nodeX == nodeY)
- {
- return true;
- }
-
- return nodeX != null && nodeX.EquivalentTo(nodeY);
- }
-
- public int GetHashCode(SyntaxTreeNode node)
- {
- if (node == null)
- {
- throw new ArgumentNullException(nameof(node));
- }
-
- return node.GetEquivalenceHash();
- }
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/BlockBuilder.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/BlockBuilder.cs
deleted file mode 100644
index 3e3905c4a7..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/BlockBuilder.cs
+++ /dev/null
@@ -1,40 +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;
-
-namespace Microsoft.AspNetCore.Razor.Language.Legacy
-{
- internal class BlockBuilder
- {
- public BlockBuilder()
- {
- Reset();
- }
-
- public BlockBuilder(Block original)
- {
- Type = original.Type;
- Children = new List(original.Children);
- ChunkGenerator = original.ChunkGenerator;
- }
-
- public IParentChunkGenerator ChunkGenerator { get; set; }
-
- public BlockKindInternal? Type { get; set; }
-
- public List Children { get; private set; }
-
- public virtual Block Build()
- {
- return new Block(this);
- }
-
- public virtual void Reset()
- {
- Type = null;
- Children = new List();
- ChunkGenerator = ParentChunkGenerator.Null;
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/BlockExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/BlockExtensions.cs
deleted file mode 100644
index 232baf7ee9..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/BlockExtensions.cs
+++ /dev/null
@@ -1,28 +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.Razor.Language.Legacy
-{
- internal static class BlockExtensions
- {
- public static void LinkNodes(this Block self)
- {
- Span first = null;
- Span previous = null;
- foreach (Span span in self.Flatten())
- {
- if (first == null)
- {
- first = span;
- }
- span.Previous = previous;
-
- if (previous != null)
- {
- previous.Next = span;
- }
- previous = span;
- }
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs
index a75c983fec..44612ee18c 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs
@@ -5,17 +5,18 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class CSharpCodeParser : TokenizerBackedParser
+ internal class CSharpCodeParser : TokenizerBackedParser
{
private static HashSet InvalidNonWhitespaceNameCharacters = new HashSet(new[]
{
'@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*'
});
- private static readonly Func IsValidStatementSpacingToken =
+ private static readonly Func IsValidStatementSpacingToken =
IsSpacingToken(includeNewLines: true, includeComments: true);
internal static readonly DirectiveDescriptor AddTagHelperDirectiveDescriptor = DirectiveDescriptor.CreateDirective(
@@ -69,8 +70,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
private readonly ISet CurrentKeywords = new HashSet(DefaultKeywords);
- private Dictionary _directiveParsers = new Dictionary(StringComparer.Ordinal);
- private Dictionary> _keywordParsers = new Dictionary>();
+ private Dictionary, CSharpTransitionSyntax>> _keywordParserMap = new Dictionary, CSharpTransitionSyntax>>();
+ private Dictionary, CSharpTransitionSyntax>> _directiveParserMap = new Dictionary, CSharpTransitionSyntax>>(StringComparer.Ordinal);
public CSharpCodeParser(ParserContext context)
: this(directives: Enumerable.Empty(), context: context)
@@ -91,9 +92,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
Keywords = new HashSet();
- SetUpKeywords();
- SetupDirectives(directives);
- SetUpExpressions();
+ SetupKeywordParsers();
+ SetupExpressionParsers();
+ SetupDirectiveParsers(directives);
}
public HtmlMarkupParser HtmlParser { get; set; }
@@ -102,262 +103,146 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public bool IsNested { get; set; }
- protected override bool TokenTypeEquals(CSharpTokenType x, CSharpTokenType y) => x == y;
-
- protected void MapDirectives(Action handler, params string[] directives)
+ public CSharpCodeBlockSyntax ParseBlock()
{
- foreach (var directive in directives)
+ if (Context == null)
{
- _directiveParsers.Add(directive, () =>
- {
- handler();
- Context.SeenDirectives.Add(directive);
- });
-
- Keywords.Add(directive);
-
- // These C# keywords are reserved for use in directives. It's an error to use them outside of
- // a directive. This code removes the error generation if the directive *is* registered.
- if (string.Equals(directive, "class", StringComparison.OrdinalIgnoreCase))
- {
- _keywordParsers.Remove(CSharpKeyword.Class);
- }
- else if (string.Equals(directive, "namespace", StringComparison.OrdinalIgnoreCase))
- {
- _keywordParsers.Remove(CSharpKeyword.Namespace);
- }
+ throw new InvalidOperationException(Resources.Parser_Context_Not_Set);
}
- }
- protected bool TryGetDirectiveHandler(string directive, out Action handler)
- {
- return _directiveParsers.TryGetValue(directive, out handler);
- }
-
- private void MapExpressionKeyword(Action handler, CSharpKeyword keyword)
- {
- _keywordParsers.Add(keyword, handler);
-
- // Expression keywords don't belong in the regular keyword list
- }
-
- private void MapKeywords(Action handler, params CSharpKeyword[] keywords)
- {
- MapKeywords(handler, topLevel: true, keywords: keywords);
- }
-
- private void MapKeywords(Action handler, bool topLevel, params CSharpKeyword[] keywords)
- {
- foreach (var keyword in keywords)
+ if (EndOfFile)
{
- _keywordParsers.Add(keyword, handler);
- if (topLevel)
- {
- Keywords.Add(CSharpLanguageCharacteristics.GetKeyword(keyword));
- }
+ // Nothing to parse.
+ return null;
}
- }
- [Conditional("DEBUG")]
- internal void Assert(CSharpKeyword expectedKeyword)
- {
- Debug.Assert(CurrentToken.Type == CSharpTokenType.Keyword &&
- CurrentToken.Keyword.HasValue &&
- CurrentToken.Keyword.Value == expectedKeyword);
- }
-
- protected internal bool At(CSharpKeyword keyword)
- {
- return At(CSharpTokenType.Keyword) &&
- CurrentToken.Keyword.HasValue &&
- CurrentToken.Keyword.Value == keyword;
- }
-
- protected internal bool AcceptIf(CSharpKeyword keyword)
- {
- if (At(keyword))
+ using (var pooledResult = Pool.Allocate())
+ using (PushSpanContextConfig(DefaultSpanContextConfig))
{
- AcceptAndMoveNext();
- return true;
- }
- return false;
- }
-
- protected static Func IsSpacingToken(bool includeNewLines, bool includeComments)
- {
- return token => token.Type == CSharpTokenType.WhiteSpace ||
- (includeNewLines && token.Type == CSharpTokenType.NewLine) ||
- (includeComments && token.Type == CSharpTokenType.Comment);
- }
-
- public override void ParseBlock()
- {
- using (PushSpanConfig(DefaultSpanConfig))
- {
- if (Context == null)
- {
- throw new InvalidOperationException(Resources.Parser_Context_Not_Set);
- }
-
- Span.Start = CurrentLocation;
-
- // Unless changed, the block is a statement block
- using (Context.Builder.StartBlock(BlockKindInternal.Statement))
+ var builder = pooledResult.Builder;
+ try
{
NextToken();
+ // Unless changed, the block is a statement block
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ builder.Add(OutputTokensAsStatementLiteral());
- var current = CurrentToken;
- if (At(CSharpTokenType.StringLiteral) &&
+ // We are usually called when the other parser sees a transition '@'. Look for it.
+ SyntaxToken transitionToken = null;
+ if (At(SyntaxKind.StringLiteral) &&
CurrentToken.Content.Length > 0 &&
CurrentToken.Content[0] == SyntaxConstants.TransitionCharacter)
{
- var split = Language.SplitToken(CurrentToken, 1, CSharpTokenType.Transition);
- current = split.Item1;
+ var split = Language.SplitToken(CurrentToken, 1, SyntaxKind.Transition);
+ transitionToken = split.Item1;
// Back up to the end of the transition
Context.Source.Position -= split.Item2.Content.Length;
NextToken();
}
- else if (At(CSharpTokenType.Transition))
+ else if (At(SyntaxKind.Transition))
{
- NextToken();
+ transitionToken = EatCurrentToken();
}
- // Accept "@" if we see it, but if we don't, that's OK. We assume we were started for a good reason
- if (current.Type == CSharpTokenType.Transition)
+ if (transitionToken == null)
{
- if (Span.Tokens.Count > 0)
+ transitionToken = SyntaxFactory.MissingToken(SyntaxKind.Transition);
+ }
+
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ var transition = GetNodeWithSpanContext(SyntaxFactory.CSharpTransition(transitionToken));
+
+ if (At(SyntaxKind.LeftBrace))
+ {
+ var statementBody = ParseStatementBody();
+ var statement = SyntaxFactory.CSharpStatement(transition, statementBody);
+ builder.Add(statement);
+ }
+ else if (At(SyntaxKind.LeftParenthesis))
+ {
+ var expressionBody = ParseExplicitExpressionBody();
+ var expression = SyntaxFactory.CSharpExplicitExpression(transition, expressionBody);
+ builder.Add(expression);
+ }
+ else if (At(SyntaxKind.Identifier))
+ {
+ if (!TryParseDirective(builder, transition, CurrentToken.Content))
{
- Output(SpanKindInternal.Code);
+ if (string.Equals(
+ CurrentToken.Content,
+ SyntaxConstants.CSharp.HelperKeyword,
+ StringComparison.Ordinal))
+ {
+ var diagnostic = RazorDiagnosticFactory.CreateParsing_HelperDirectiveNotAvailable(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length));
+ CurrentToken.SetDiagnostics(new[] { diagnostic });
+ Context.ErrorSink.OnError(diagnostic);
+ }
+
+ var implicitExpressionBody = ParseImplicitExpressionBody();
+ var implicitExpression = SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody);
+ builder.Add(implicitExpression);
}
- AtTransition(current);
+ }
+ else if (At(SyntaxKind.Keyword))
+ {
+ if (!TryParseDirective(builder, transition, CurrentToken.Content) &&
+ !TryParseKeyword(builder, transition))
+ {
+ // Not a directive or a special keyword. Just parse as an implicit expression.
+ var implicitExpressionBody = ParseImplicitExpressionBody();
+ var implicitExpression = SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody);
+ builder.Add(implicitExpression);
+ }
+
+ builder.Add(OutputTokensAsStatementLiteral());
}
else
{
- // No "@" => Jump straight to AfterTransition
- AfterTransition();
- }
-
- Output(SpanKindInternal.Code);
- }
- }
- }
-
- private void DefaultSpanConfig(SpanBuilder span)
- {
- span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
- span.ChunkGenerator = new StatementChunkGenerator();
- }
-
- private void AtTransition(CSharpToken current)
- {
- Debug.Assert(current.Type == CSharpTokenType.Transition);
- Accept(current);
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- Span.ChunkGenerator = SpanChunkGenerator.Null;
-
- // Output the "@" span and continue here
- Output(SpanKindInternal.Transition);
- AfterTransition();
- }
-
- private void AfterTransition()
- {
- using (PushSpanConfig(DefaultSpanConfig))
- {
- EnsureCurrent();
- try
- {
- // What type of block is this?
- if (!EndOfFile)
- {
- if (CurrentToken.Type == CSharpTokenType.LeftParenthesis)
+ // Invalid character
+ SpanContext.ChunkGenerator = new ExpressionChunkGenerator();
+ SpanContext.EditHandler = new ImplicitExpressionEditHandler(
+ Language.TokenizeString,
+ CurrentKeywords,
+ acceptTrailingDot: IsNested)
{
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Expression;
- Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
- ExplicitExpression();
- return;
+ AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace
+ };
+
+ AcceptMarkerTokenIfNecessary();
+ var expressionLiteral = SyntaxFactory.CSharpCodeBlock(OutputTokensAsExpressionLiteral());
+ var expressionBody = SyntaxFactory.CSharpImplicitExpressionBody(expressionLiteral);
+ var expressionBlock = SyntaxFactory.CSharpImplicitExpression(transition, expressionBody);
+ builder.Add(expressionBlock);
+
+ if (At(SyntaxKind.Whitespace) || At(SyntaxKind.NewLine))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedWhiteSpaceAtStartOfCodeBlock(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length)));
}
- else if (CurrentToken.Type == CSharpTokenType.Identifier)
+ else if (EndOfFile)
{
- if (TryGetDirectiveHandler(CurrentToken.Content, out var handler))
- {
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- handler();
- return;
- }
- else
- {
- if (string.Equals(
- CurrentToken.Content,
- SyntaxConstants.CSharp.HelperKeyword,
- StringComparison.Ordinal))
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_HelperDirectiveNotAvailable(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length)));
- }
-
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Expression;
- Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
- ImplicitExpression();
- return;
- }
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedEndOfFileAtStartOfCodeBlock(
+ new SourceSpan(CurrentStart, contentLength: 1 /* end of file */)));
}
- else if (CurrentToken.Type == CSharpTokenType.Keyword)
+ else
{
- if (TryGetDirectiveHandler(CurrentToken.Content, out var handler))
- {
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- handler();
- return;
- }
- else
- {
- KeywordBlock(topLevel: true);
- return;
- }
- }
- else if (CurrentToken.Type == CSharpTokenType.LeftBrace)
- {
- VerbatimBlock();
- return;
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedCharacterAtStartOfCodeBlock(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length),
+ CurrentToken.Content));
}
}
- // Invalid character
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Expression;
- Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
- AddMarkerTokenIfNecessary();
- Span.ChunkGenerator = new ExpressionChunkGenerator();
- Span.EditHandler = new ImplicitExpressionEditHandler(
- Language.TokenizeString,
- CurrentKeywords,
- acceptTrailingDot: IsNested)
- {
- AcceptedCharacters = AcceptedCharactersInternal.NonWhiteSpace
- };
- if (At(CSharpTokenType.WhiteSpace) || At(CSharpTokenType.NewLine))
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_UnexpectedWhiteSpaceAtStartOfCodeBlock(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length)));
- }
- else if (EndOfFile)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_UnexpectedEndOfFileAtStartOfCodeBlock(
- new SourceSpan(CurrentStart, contentLength: 1 /* end of file */)));
- }
- else
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_UnexpectedCharacterAtStartOfCodeBlock(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length),
- CurrentToken.Content));
- }
+ Debug.Assert(TokenBuilder.Count == 0, "We should not have any tokens left.");
+
+ var codeBlock = SyntaxFactory.CSharpCodeBlock(builder.ToList());
+ return codeBlock;
}
finally
{
@@ -367,75 +252,90 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
}
- private void VerbatimBlock()
+ private CSharpExplicitExpressionBodySyntax ParseExplicitExpressionBody()
{
- Assert(CSharpTokenType.LeftBrace);
- var block = new Block(Resources.BlockName_Code, CurrentStart);
- AcceptAndMoveNext();
+ var block = new Block(Resources.BlockName_ExplicitExpression, CurrentStart);
+ Assert(SyntaxKind.LeftParenthesis);
+ var leftParenToken = EatCurrentToken();
+ var leftParen = OutputAsMetaCode(leftParenToken);
- // Set up the "{" span and output
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.MetaCode);
-
- // Set up auto-complete and parse the code block
- var editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
- Span.EditHandler = editHandler;
- CodeBlock(false, block);
-
- Span.ChunkGenerator = new StatementChunkGenerator();
- AddMarkerTokenIfNecessary();
- if (!At(CSharpTokenType.RightBrace))
+ using (var pooledResult = Pool.Allocate())
{
- editHandler.AutoCompleteString = "}";
- }
- Output(SpanKindInternal.Code);
-
- if (Optional(CSharpTokenType.RightBrace))
- {
- // Set up the "}" span
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- }
-
- if (!IsNested)
- {
- EnsureCurrent();
- if (At(CSharpTokenType.NewLine) ||
- (At(CSharpTokenType.WhiteSpace) && NextIs(CSharpTokenType.NewLine)))
+ var expressionBuilder = pooledResult.Builder;
+ using (PushSpanContextConfig(ExplicitExpressionSpanContextConfig))
{
- Context.NullGenerateWhitespaceAndNewLine = true;
+ var success = Balance(
+ expressionBuilder,
+ BalancingModes.BacktrackOnFailure |
+ BalancingModes.NoErrorOnFailure |
+ BalancingModes.AllowCommentsAndTemplates,
+ SyntaxKind.LeftParenthesis,
+ SyntaxKind.RightParenthesis,
+ block.Start);
+
+ if (!success)
+ {
+ AcceptUntil(SyntaxKind.LessThan);
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
+ new SourceSpan(block.Start, contentLength: 1 /* ( */), block.Name, ")", "("));
+ }
+
+ // If necessary, put an empty-content marker token here
+ AcceptMarkerTokenIfNecessary();
+ expressionBuilder.Add(OutputTokensAsExpressionLiteral());
}
+
+ var expressionBlock = SyntaxFactory.CSharpCodeBlock(expressionBuilder.ToList());
+
+ RazorMetaCodeSyntax rightParen = null;
+ if (At(SyntaxKind.RightParenthesis))
+ {
+ rightParen = OutputAsMetaCode(EatCurrentToken());
+ }
+ else
+ {
+ var missingToken = SyntaxFactory.MissingToken(SyntaxKind.RightParenthesis);
+ rightParen = OutputAsMetaCode(missingToken, SpanContext.EditHandler.AcceptedCharacters);
+ }
+ if (!EndOfFile)
+ {
+ PutCurrentBack();
+ }
+
+ return SyntaxFactory.CSharpExplicitExpressionBody(leftParen, expressionBlock, rightParen);
+ }
+ }
+
+ private CSharpImplicitExpressionBodySyntax ParseImplicitExpressionBody(bool async = false)
+ {
+ var accepted = AcceptedCharactersInternal.NonWhitespace;
+ if (async)
+ {
+ // Async implicit expressions include the "await" keyword and therefore need to allow spaces to
+ // separate the "await" and the following code.
+ accepted = AcceptedCharactersInternal.AnyExceptNewline;
}
- Output(SpanKindInternal.MetaCode);
- }
-
- private void ImplicitExpression()
- {
- ImplicitExpression(AcceptedCharactersInternal.NonWhiteSpace);
- }
-
- // Async implicit expressions include the "await" keyword and therefore need to allow spaces to
- // separate the "await" and the following code.
- private void AsyncImplicitExpression()
- {
- ImplicitExpression(AcceptedCharactersInternal.AnyExceptNewline);
- }
-
- private void ImplicitExpression(AcceptedCharactersInternal acceptedCharacters)
- {
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Expression;
- Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
-
- using (PushSpanConfig(span =>
+ using (var pooledResult = Pool.Allocate())
{
- span.EditHandler = new ImplicitExpressionEditHandler(
+ var expressionBuilder = pooledResult.Builder;
+ ParseImplicitExpression(expressionBuilder, accepted);
+ var codeBlock = SyntaxFactory.CSharpCodeBlock(expressionBuilder.ToList());
+ return SyntaxFactory.CSharpImplicitExpressionBody(codeBlock);
+ }
+ }
+
+ private void ParseImplicitExpression(in SyntaxListBuilder builder, AcceptedCharactersInternal acceptedCharacters)
+ {
+ using (PushSpanContextConfig(spanContext =>
+ {
+ spanContext.EditHandler = new ImplicitExpressionEditHandler(
Language.TokenizeString,
Keywords,
acceptTrailingDot: IsNested);
- span.EditHandler.AcceptedCharacters = acceptedCharacters;
- span.ChunkGenerator = new ExpressionChunkGenerator();
+ spanContext.EditHandler.AcceptedCharacters = acceptedCharacters;
+ spanContext.ChunkGenerator = new ExpressionChunkGenerator();
}))
{
do
@@ -445,80 +345,80 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
AcceptAndMoveNext();
}
}
- while (MethodCallOrArrayIndex(acceptedCharacters));
+ while (ParseMethodCallOrArrayIndex(builder, acceptedCharacters));
PutCurrentBack();
- Output(SpanKindInternal.Code);
+ builder.Add(OutputTokensAsExpressionLiteral());
}
}
- private bool MethodCallOrArrayIndex(AcceptedCharactersInternal acceptedCharacters)
+ private bool ParseMethodCallOrArrayIndex(in SyntaxListBuilder builder, AcceptedCharactersInternal acceptedCharacters)
{
if (!EndOfFile)
{
- if (CurrentToken.Type == CSharpTokenType.LeftParenthesis ||
- CurrentToken.Type == CSharpTokenType.LeftBracket)
+ if (CurrentToken.Kind == SyntaxKind.LeftParenthesis ||
+ CurrentToken.Kind == SyntaxKind.LeftBracket)
{
// If we end within "(", whitespace is fine
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
- CSharpTokenType right;
+ SyntaxKind right;
bool success;
- using (PushSpanConfig((span, prev) =>
+ using (PushSpanContextConfig((spanContext, prev) =>
{
- prev(span);
- span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ prev(spanContext);
+ spanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
}))
{
- right = Language.FlipBracket(CurrentToken.Type);
- success = Balance(BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
+ right = Language.FlipBracket(CurrentToken.Kind);
+ success = Balance(builder, BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
}
if (!success)
{
- AcceptUntil(CSharpTokenType.LessThan);
+ AcceptUntil(SyntaxKind.LessThan);
}
if (At(right))
{
AcceptAndMoveNext();
// At the ending brace, restore the initial accepted characters.
- Span.EditHandler.AcceptedCharacters = acceptedCharacters;
+ SpanContext.EditHandler.AcceptedCharacters = acceptedCharacters;
}
- return MethodCallOrArrayIndex(acceptedCharacters);
+ return ParseMethodCallOrArrayIndex(builder, acceptedCharacters);
}
- if (At(CSharpTokenType.QuestionMark))
+ if (At(SyntaxKind.QuestionMark))
{
var next = Lookahead(count: 1);
if (next != null)
{
- if (next.Type == CSharpTokenType.Dot)
+ if (next.Kind == SyntaxKind.Dot)
{
// Accept null conditional dot operator (?.).
AcceptAndMoveNext();
AcceptAndMoveNext();
// If the next piece after the ?. is a keyword or identifier then we want to continue.
- return At(CSharpTokenType.Identifier) || At(CSharpTokenType.Keyword);
+ return At(SyntaxKind.Identifier) || At(SyntaxKind.Keyword);
}
- else if (next.Type == CSharpTokenType.LeftBracket)
+ else if (next.Kind == SyntaxKind.LeftBracket)
{
// We're at the ? for a null conditional bracket operator (?[).
AcceptAndMoveNext();
// Accept the [ and any content inside (it will attempt to balance).
- return MethodCallOrArrayIndex(acceptedCharacters);
+ return ParseMethodCallOrArrayIndex(builder, acceptedCharacters);
}
}
}
- else if (At(CSharpTokenType.Dot))
+ else if (At(SyntaxKind.Dot))
{
var dot = CurrentToken;
if (NextToken())
{
- if (At(CSharpTokenType.Identifier) || At(CSharpTokenType.Keyword))
+ if (At(SyntaxKind.Identifier) || At(SyntaxKind.Keyword))
{
// Accept the dot and return to the start
Accept(dot);
@@ -540,7 +440,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Accept(dot);
}
}
- else if (!At(CSharpTokenType.WhiteSpace) && !At(CSharpTokenType.NewLine))
+ else if (!At(SyntaxKind.Whitespace) && !At(SyntaxKind.NewLine))
{
PutCurrentBack();
}
@@ -550,746 +450,107 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return false;
}
- protected void CompleteBlock()
+ private CSharpStatementBodySyntax ParseStatementBody(Block block = null)
{
- CompleteBlock(insertMarkerIfNecessary: true);
- }
-
- protected void CompleteBlock(bool insertMarkerIfNecessary)
- {
- CompleteBlock(insertMarkerIfNecessary, captureWhitespaceToEndOfLine: insertMarkerIfNecessary);
- }
-
- protected void CompleteBlock(bool insertMarkerIfNecessary, bool captureWhitespaceToEndOfLine)
- {
- if (insertMarkerIfNecessary && Context.Builder.LastAcceptedCharacters != AcceptedCharactersInternal.Any)
+ Assert(SyntaxKind.LeftBrace);
+ block = block ?? new Block(Resources.BlockName_Code, CurrentStart);
+ var leftBrace = OutputAsMetaCode(EatExpectedToken(SyntaxKind.LeftBrace));
+ CSharpCodeBlockSyntax codeBlock = null;
+ using (var pooledResult = Pool.Allocate())
{
- AddMarkerTokenIfNecessary();
- }
-
- EnsureCurrent();
-
- // Read whitespace, but not newlines
- // If we're not inserting a marker span, we don't need to capture whitespace
- if (!Context.WhiteSpaceIsSignificantToAncestorBlock &&
- Context.Builder.CurrentBlock.Type != BlockKindInternal.Expression &&
- captureWhitespaceToEndOfLine &&
- !Context.DesignTimeMode &&
- !IsNested)
- {
- CaptureWhitespaceAtEndOfCodeOnlyLine();
- }
- else
- {
- PutCurrentBack();
- }
- }
-
- private void CaptureWhitespaceAtEndOfCodeOnlyLine()
- {
- var whitespace = ReadWhile(token => token.Type == CSharpTokenType.WhiteSpace);
- if (At(CSharpTokenType.NewLine))
- {
- Accept(whitespace);
- AcceptAndMoveNext();
- PutCurrentBack();
- }
- else
- {
- PutCurrentBack();
- PutBack(whitespace);
- }
- }
-
- private void ConfigureExplicitExpressionSpan(SpanBuilder sb)
- {
- sb.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
- sb.ChunkGenerator = new ExpressionChunkGenerator();
- }
-
- private void ExplicitExpression()
- {
- var block = new Block(Resources.BlockName_ExplicitExpression, CurrentStart);
- Assert(CSharpTokenType.LeftParenthesis);
- AcceptAndMoveNext();
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.MetaCode);
- using (PushSpanConfig(ConfigureExplicitExpressionSpan))
- {
- var success = Balance(
- BalancingModes.BacktrackOnFailure |
- BalancingModes.NoErrorOnFailure |
- BalancingModes.AllowCommentsAndTemplates,
- CSharpTokenType.LeftParenthesis,
- CSharpTokenType.RightParenthesis,
- block.Start);
-
- if (!success)
- {
- AcceptUntil(CSharpTokenType.LessThan);
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
- new SourceSpan(block.Start, contentLength: 1 /* ( */), block.Name, ")", "("));
- }
-
- // If necessary, put an empty-content marker token here
- if (Span.Tokens.Count == 0)
- {
- Accept(new CSharpToken(string.Empty, CSharpTokenType.Unknown));
- }
-
- // Output the content span and then capture the ")"
- Output(SpanKindInternal.Code);
- }
- Optional(CSharpTokenType.RightParenthesis);
- if (!EndOfFile)
- {
- PutCurrentBack();
- }
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- CompleteBlock(insertMarkerIfNecessary: false);
- Output(SpanKindInternal.MetaCode);
- }
-
- private void Template()
- {
- if (Context.Builder.ActiveBlocks.Any(block => block.Type == BlockKindInternal.Template))
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_InlineMarkupBlocksCannotBeNested(
- new SourceSpan(CurrentStart, contentLength: 1 /* @ */)));
- }
- Output(SpanKindInternal.Code);
- using (Context.Builder.StartBlock(BlockKindInternal.Template))
- {
- Context.Builder.CurrentBlock.ChunkGenerator = new TemplateBlockChunkGenerator();
- PutCurrentBack();
- OtherParserBlock();
- }
- }
-
- private void OtherParserBlock()
- {
- ParseWithOtherParser(p => p.ParseBlock());
- }
-
- private void SectionBlock(string left, string right, bool caseSensitive)
- {
- ParseWithOtherParser(p => p.ParseRazorBlock(Tuple.Create(left, right), caseSensitive));
- }
-
- private void NestedBlock()
- {
- Output(SpanKindInternal.Code);
-
- var wasNested = IsNested;
- IsNested = true;
- using (PushSpanConfig())
- {
- ParseBlock();
- }
-
- Span.Start = CurrentLocation;
- Initialize(Span);
- IsNested = wasNested;
- NextToken();
- }
-
- protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
- {
- // No embedded transitions in C#, so ignore that param
- return allowTemplatesAndComments
- && ((Language.IsTransition(CurrentToken)
- && NextIs(CSharpTokenType.LessThan, CSharpTokenType.Colon, CSharpTokenType.DoubleColon))
- || Language.IsCommentStart(CurrentToken));
- }
-
- protected override void HandleEmbeddedTransition()
- {
- if (Language.IsTransition(CurrentToken))
- {
- PutCurrentBack();
- Template();
- }
- else if (Language.IsCommentStart(CurrentToken))
- {
- RazorComment();
- }
- }
-
- private void ParseWithOtherParser(Action parseAction)
- {
- // When transitioning to the HTML parser we no longer want to act as if we're in a nested C# state.
- // For instance, if @hello.
is in a nested C# block we don't want the trailing '.' to be handled
- // as C#; it should be handled as a period because it's wrapped in markup.
- var wasNested = IsNested;
- IsNested = false;
-
- using (PushSpanConfig())
- {
- parseAction(HtmlParser);
- }
-
- Span.Start = CurrentLocation;
- Initialize(Span);
-
- IsNested = wasNested;
-
- NextToken();
- }
-
- private void SetUpKeywords()
- {
- MapKeywords(
- ConditionalBlock,
- CSharpKeyword.For,
- CSharpKeyword.Foreach,
- CSharpKeyword.While,
- CSharpKeyword.Switch,
- CSharpKeyword.Lock);
- MapKeywords(CaseStatement, false, CSharpKeyword.Case, CSharpKeyword.Default);
- MapKeywords(IfStatement, CSharpKeyword.If);
- MapKeywords(TryStatement, CSharpKeyword.Try);
- MapKeywords(UsingKeyword, CSharpKeyword.Using);
- MapKeywords(DoStatement, CSharpKeyword.Do);
- MapKeywords(ReservedDirective, CSharpKeyword.Class, CSharpKeyword.Namespace);
- }
-
- protected virtual void ReservedDirective(bool topLevel)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_ReservedWord(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length), CurrentToken.Content));
-
- AcceptAndMoveNext();
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Directive;
- CompleteBlock();
- Output(SpanKindInternal.MetaCode);
- }
-
- private void KeywordBlock(bool topLevel)
- {
- HandleKeyword(topLevel, () =>
- {
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Expression;
- Context.Builder.CurrentBlock.ChunkGenerator = new ExpressionChunkGenerator();
- ImplicitExpression();
- });
- }
-
- private void CaseStatement(bool topLevel)
- {
- Assert(CSharpTokenType.Keyword);
- Debug.Assert(CurrentToken.Keyword != null &&
- (CurrentToken.Keyword.Value == CSharpKeyword.Case ||
- CurrentToken.Keyword.Value == CSharpKeyword.Default));
- AcceptUntil(CSharpTokenType.Colon);
- Optional(CSharpTokenType.Colon);
- }
-
- private void DoStatement(bool topLevel)
- {
- Assert(CSharpKeyword.Do);
- UnconditionalBlock();
- WhileClause();
- if (topLevel)
- {
- CompleteBlock();
- }
- }
-
- private void WhileClause()
- {
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
- var whitespace = SkipToNextImportantToken();
-
- if (At(CSharpKeyword.While))
- {
- Accept(whitespace);
- Assert(CSharpKeyword.While);
- AcceptAndMoveNext();
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- if (AcceptCondition() && Optional(CSharpTokenType.Semicolon))
- {
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- }
- }
- else
- {
- PutCurrentBack();
- PutBack(whitespace);
- }
- }
-
- private void UsingKeyword(bool topLevel)
- {
- Assert(CSharpKeyword.Using);
- var block = new Block(CurrentToken, CurrentStart);
- AcceptAndMoveNext();
- AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
-
- if (At(CSharpTokenType.LeftParenthesis))
- {
- // using ( ==> Using Statement
- UsingStatement(block);
- }
- else if (At(CSharpTokenType.Identifier) || At(CSharpKeyword.Static))
- {
- // using Identifier ==> Using Declaration
- if (!topLevel)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_NamespaceImportAndTypeAliasCannotExistWithinCodeBlock(
- new SourceSpan(block.Start, block.Name.Length)));
- StandardStatement();
- }
- else
- {
- UsingDeclaration();
- }
- }
-
- if (topLevel)
- {
- CompleteBlock();
- }
- }
-
- private void UsingDeclaration()
- {
- // Set block type to directive
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Directive;
-
- var start = CurrentStart;
- if (At(CSharpTokenType.Identifier))
- {
- // non-static using
- NamespaceOrTypeName();
- var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- if (At(CSharpTokenType.Assign))
- {
- // Alias
- Accept(whitespace);
- Assert(CSharpTokenType.Assign);
- AcceptAndMoveNext();
-
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
-
- // One more namespace or type name
- NamespaceOrTypeName();
- }
- else
- {
- PutCurrentBack();
- PutBack(whitespace);
- }
- }
- else if (At(CSharpKeyword.Static))
- {
- // static using
- AcceptAndMoveNext();
- AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
- NamespaceOrTypeName();
- }
-
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AnyExceptNewline;
- Span.ChunkGenerator = new AddImportChunkGenerator(new LocationTagged(
- string.Concat(Span.Tokens.Skip(1).Select(s => s.Content)),
- start));
-
- // Optional ";"
- if (EnsureCurrent())
- {
- Optional(CSharpTokenType.Semicolon);
- }
- }
-
- // Used for parsing a qualified name like that which follows the `namespace` keyword.
- //
- // qualified-identifier:
- // identifier
- // qualified-identifier . identifier
- protected bool QualifiedIdentifier(out int identifierLength)
- {
- var currentIdentifierLength = 0;
- var expectingDot = false;
- var tokens = ReadWhile(token =>
- {
- var type = token.Type;
- if ((expectingDot && type == CSharpTokenType.Dot) ||
- (!expectingDot && type == CSharpTokenType.Identifier))
- {
- expectingDot = !expectingDot;
- return true;
- }
-
- if (type != CSharpTokenType.WhiteSpace &&
- type != CSharpTokenType.NewLine)
- {
- expectingDot = false;
- currentIdentifierLength += token.Content.Length;
- }
-
- return false;
- });
-
- identifierLength = currentIdentifierLength;
- var validQualifiedIdentifier = expectingDot;
- if (validQualifiedIdentifier)
- {
- foreach (var token in tokens)
- {
- identifierLength += token.Content.Length;
- Accept(token);
- }
-
- return true;
- }
- else
- {
- PutCurrentBack();
-
- foreach (var token in tokens)
- {
- identifierLength += token.Content.Length;
- PutBack(token);
- }
+ var builder = pooledResult.Builder;
+ // Set up auto-complete and parse the code block
+ var editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
+ SpanContext.EditHandler = editHandler;
+ ParseCodeBlock(builder, block, acceptTerminatingBrace: false);
EnsureCurrent();
- return false;
+ SpanContext.ChunkGenerator = new StatementChunkGenerator();
+ AcceptMarkerTokenIfNecessary();
+ if (!At(SyntaxKind.RightBrace))
+ {
+ editHandler.AutoCompleteString = "}";
+ }
+ builder.Add(OutputTokensAsStatementLiteral());
+
+ codeBlock = SyntaxFactory.CSharpCodeBlock(builder.ToList());
}
- }
- protected bool NamespaceOrTypeName()
- {
- if (Optional(CSharpTokenType.LeftParenthesis))
+ RazorMetaCodeSyntax rightBrace = null;
+ if (At(SyntaxKind.RightBrace))
{
- while (!Optional(CSharpTokenType.RightParenthesis) && !EndOfFile)
- {
- Optional(CSharpTokenType.WhiteSpace);
-
- if (!NamespaceOrTypeName())
- {
- return false;
- }
-
- Optional(CSharpTokenType.WhiteSpace);
- Optional(CSharpTokenType.Identifier);
- Optional(CSharpTokenType.WhiteSpace);
- Optional(CSharpTokenType.Comma);
- }
-
- if (At(CSharpTokenType.WhiteSpace) && NextIs(CSharpTokenType.QuestionMark))
- {
- // Only accept the whitespace if we are going to consume the next token.
- AcceptAndMoveNext();
- }
-
- Optional(CSharpTokenType.QuestionMark); // Nullable
-
- return true;
- }
- else if (Optional(CSharpTokenType.Identifier) || Optional(CSharpTokenType.Keyword))
- {
- if (Optional(CSharpTokenType.DoubleColon))
- {
- if (!Optional(CSharpTokenType.Identifier))
- {
- Optional(CSharpTokenType.Keyword);
- }
- }
- if (At(CSharpTokenType.LessThan))
- {
- TypeArgumentList();
- }
- if (Optional(CSharpTokenType.Dot))
- {
- NamespaceOrTypeName();
- }
-
- if (At(CSharpTokenType.WhiteSpace) && NextIs(CSharpTokenType.QuestionMark))
- {
- // Only accept the whitespace if we are going to consume the next token.
- AcceptAndMoveNext();
- }
-
- Optional(CSharpTokenType.QuestionMark); // Nullable
-
- if (At(CSharpTokenType.WhiteSpace) && NextIs(CSharpTokenType.LeftBracket))
- {
- // Only accept the whitespace if we are going to consume the next token.
- AcceptAndMoveNext();
- }
-
- while (At(CSharpTokenType.LeftBracket))
- {
- Balance(BalancingModes.None);
- Optional(CSharpTokenType.RightBracket);
- }
- return true;
+ rightBrace = OutputAsMetaCode(EatCurrentToken());
}
else
{
- return false;
+ rightBrace = OutputAsMetaCode(
+ SyntaxFactory.MissingToken(SyntaxKind.RightBrace),
+ SpanContext.EditHandler.AcceptedCharacters);
}
- }
- private void TypeArgumentList()
- {
- Assert(CSharpTokenType.LessThan);
- Balance(BalancingModes.None);
- Optional(CSharpTokenType.GreaterThan);
- }
-
- private void UsingStatement(Block block)
- {
- Assert(CSharpTokenType.LeftParenthesis);
-
- // Parse condition
- if (AcceptCondition())
+ if (!IsNested)
{
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
-
- // Parse code block
- ExpectCodeBlock(block);
- }
- }
-
- private void TryStatement(bool topLevel)
- {
- Assert(CSharpKeyword.Try);
- UnconditionalBlock();
- AfterTryClause();
- if (topLevel)
- {
- CompleteBlock();
- }
- }
-
- private void IfStatement(bool topLevel)
- {
- Assert(CSharpKeyword.If);
- ConditionalBlock(topLevel: false);
- AfterIfClause();
- if (topLevel)
- {
- CompleteBlock();
- }
- }
-
- private void AfterTryClause()
- {
- // Grab whitespace
- var whitespace = SkipToNextImportantToken();
-
- // Check for a catch or finally part
- if (At(CSharpKeyword.Catch))
- {
- Accept(whitespace);
- Assert(CSharpKeyword.Catch);
- FilterableCatchBlock();
- AfterTryClause();
- }
- else if (At(CSharpKeyword.Finally))
- {
- Accept(whitespace);
- Assert(CSharpKeyword.Finally);
- UnconditionalBlock();
- }
- else
- {
- // Return whitespace and end the block
- PutCurrentBack();
- PutBack(whitespace);
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
- }
- }
-
- private void AfterIfClause()
- {
- // Grab whitespace and razor comments
- var whitespace = SkipToNextImportantToken();
-
- // Check for an else part
- if (At(CSharpKeyword.Else))
- {
- Accept(whitespace);
- Assert(CSharpKeyword.Else);
- ElseClause();
- }
- else
- {
- // No else, return whitespace
- PutCurrentBack();
- PutBack(whitespace);
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
- }
- }
-
- private void ElseClause()
- {
- if (!At(CSharpKeyword.Else))
- {
- return;
- }
- var block = new Block(CurrentToken, CurrentStart);
-
- AcceptAndMoveNext();
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- if (At(CSharpKeyword.If))
- {
- // ElseIf
- block.Name = SyntaxConstants.CSharp.ElseIfKeyword;
- ConditionalBlock(block);
- AfterIfClause();
- }
- else if (!EndOfFile)
- {
- // Else
- ExpectCodeBlock(block);
- }
- }
-
- private void ExpectCodeBlock(Block block)
- {
- if (!EndOfFile)
- {
- // Check for "{" to make sure we're at a block
- if (!At(CSharpTokenType.LeftBrace))
+ EnsureCurrent();
+ if (At(SyntaxKind.NewLine) ||
+ (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.NewLine)))
{
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_SingleLineControlFlowStatementsNotAllowed(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length),
- Language.GetSample(CSharpTokenType.LeftBrace),
- CurrentToken.Content));
+ Context.NullGenerateWhitespaceAndNewLine = true;
}
-
- // Parse the statement and then we're done
- Statement(block);
}
+
+ return SyntaxFactory.CSharpStatementBody(leftBrace, codeBlock, rightBrace);
}
- private void UnconditionalBlock()
+ private void ParseCodeBlock(in SyntaxListBuilder builder, Block block, bool acceptTerminatingBrace = true)
{
- Assert(CSharpTokenType.Keyword);
- var block = new Block(CurrentToken, CurrentStart);
- AcceptAndMoveNext();
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- ExpectCodeBlock(block);
- }
-
- private void FilterableCatchBlock()
- {
- Assert(CSharpKeyword.Catch);
-
- var block = new Block(CurrentToken, CurrentStart);
-
- // Accept "catch"
- AcceptAndMoveNext();
- AcceptWhile(IsValidStatementSpacingToken);
-
- // Parse the catch condition if present. If not present, let the C# compiler complain.
- if (AcceptCondition())
+ EnsureCurrent();
+ while (!EndOfFile && !At(SyntaxKind.RightBrace))
{
- AcceptWhile(IsValidStatementSpacingToken);
-
- if (At(CSharpKeyword.When))
- {
- // Accept "when".
- AcceptAndMoveNext();
- AcceptWhile(IsValidStatementSpacingToken);
-
- // Parse the filter condition if present. If not present, let the C# compiler complain.
- if (!AcceptCondition())
- {
- // Incomplete condition.
- return;
- }
-
- AcceptWhile(IsValidStatementSpacingToken);
- }
-
- ExpectCodeBlock(block);
+ // Parse a statement, then return here
+ ParseStatement(builder, block: block);
+ EnsureCurrent();
}
- }
- private void ConditionalBlock(bool topLevel)
- {
- Assert(CSharpTokenType.Keyword);
- var block = new Block(CurrentToken, CurrentStart);
- ConditionalBlock(block);
- if (topLevel)
+ if (EndOfFile)
{
- CompleteBlock();
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
+ new SourceSpan(block.Start, contentLength: 1 /* { OR } */), block.Name, "}", "{"));
}
- }
-
- private void ConditionalBlock(Block block)
- {
- AcceptAndMoveNext();
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
-
- // Parse the condition, if present (if not present, we'll let the C# compiler complain)
- if (AcceptCondition())
+ else if (acceptTerminatingBrace)
{
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- ExpectCodeBlock(block);
+ Assert(SyntaxKind.RightBrace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ AcceptAndMoveNext();
}
}
- private bool AcceptCondition()
+ private void ParseStatement(in SyntaxListBuilder builder, Block block)
{
- if (At(CSharpTokenType.LeftParenthesis))
- {
- var complete = Balance(BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
- if (!complete)
- {
- AcceptUntil(CSharpTokenType.NewLine);
- }
- else
- {
- Optional(CSharpTokenType.RightParenthesis);
- }
- return complete;
- }
- return true;
- }
-
- private void Statement()
- {
- Statement(null);
- }
-
- private void Statement(Block block)
- {
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
-
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
// Accept whitespace but always keep the last whitespace node so we can put it back if necessary
- var lastWhitespace = AcceptWhiteSpaceInLines();
-
+ var lastWhitespace = AcceptWhitespaceInLines();
if (EndOfFile)
{
if (lastWhitespace != null)
{
Accept(lastWhitespace);
}
+
+ builder.Add(OutputTokensAsStatementLiteral());
return;
}
- var type = CurrentToken.Type;
- var loc = CurrentStart;
+ var kind = CurrentToken.Kind;
+ var location = CurrentStart;
// Both cases @: and @:: are triggered as markup, second colon in second case will be triggered as a plain text
- var isSingleLineMarkup = type == CSharpTokenType.Transition &&
- (NextIs(CSharpTokenType.Colon, CSharpTokenType.DoubleColon));
+ var isSingleLineMarkup = kind == SyntaxKind.Transition &&
+ (NextIs(SyntaxKind.Colon, SyntaxKind.DoubleColon));
var isMarkup = isSingleLineMarkup ||
- type == CSharpTokenType.LessThan ||
- (type == CSharpTokenType.Transition && NextIs(CSharpTokenType.LessThan));
+ kind == SyntaxKind.LessThan ||
+ (kind == SyntaxKind.Transition && NextIs(SyntaxKind.LessThan));
if (Context.DesignTimeMode || !isMarkup)
{
@@ -1321,90 +582,89 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
if (isMarkup)
{
- if (type == CSharpTokenType.Transition && !isSingleLineMarkup)
+ if (kind == SyntaxKind.Transition && !isSingleLineMarkup)
{
Context.ErrorSink.OnError(
RazorDiagnosticFactory.CreateParsing_AtInCodeMustBeFollowedByColonParenOrIdentifierStart(
- new SourceSpan(loc, contentLength: 1 /* @ */)));
+ new SourceSpan(location, contentLength: 1 /* @ */)));
}
// Markup block
- Output(SpanKindInternal.Code);
+ builder.Add(OutputTokensAsStatementLiteral());
if (Context.DesignTimeMode && CurrentToken != null &&
- (CurrentToken.Type == CSharpTokenType.LessThan || CurrentToken.Type == CSharpTokenType.Transition))
+ (CurrentToken.Kind == SyntaxKind.LessThan || CurrentToken.Kind == SyntaxKind.Transition))
{
PutCurrentBack();
}
- OtherParserBlock();
+ OtherParserBlock(builder);
}
else
{
// What kind of statement is this?
- HandleStatement(block, type);
+ switch (kind)
+ {
+ case SyntaxKind.RazorCommentTransition:
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
+ var comment = ParseRazorComment();
+ builder.Add(comment);
+ ParseStatement(builder, block);
+ break;
+ case SyntaxKind.LeftBrace:
+ // Verbatim Block
+ AcceptAndMoveNext();
+ ParseCodeBlock(builder, block);
+ break;
+ case SyntaxKind.Keyword:
+ if (!TryParseKeyword(builder, transition: null))
+ {
+ ParseStandardStatement(builder);
+ }
+ break;
+ case SyntaxKind.Transition:
+ // Embedded Expression block
+ ParseEmbeddedExpression(builder);
+ break;
+ case SyntaxKind.RightBrace:
+ // Possible end of Code Block, just run the continuation
+ break;
+ case SyntaxKind.CSharpComment:
+ Accept(CurrentToken);
+ NextToken();
+ break;
+ default:
+ // Other statement
+ ParseStandardStatement(builder);
+ break;
+ }
}
}
- private void HandleStatement(Block block, CSharpTokenType type)
- {
- switch (type)
- {
- case CSharpTokenType.RazorCommentTransition:
- Output(SpanKindInternal.Code);
- RazorComment();
- Statement(block);
- break;
- case CSharpTokenType.LeftBrace:
- // Verbatim Block
- block = block ?? new Block(Resources.BlockName_Code, CurrentStart);
- AcceptAndMoveNext();
- CodeBlock(block);
- break;
- case CSharpTokenType.Keyword:
- // Keyword block
- HandleKeyword(false, StandardStatement);
- break;
- case CSharpTokenType.Transition:
- // Embedded Expression block
- EmbeddedExpression();
- break;
- case CSharpTokenType.RightBrace:
- // Possible end of Code Block, just run the continuation
- break;
- case CSharpTokenType.Comment:
- AcceptAndMoveNext();
- break;
- default:
- // Other statement
- StandardStatement();
- break;
- }
- }
-
- private void EmbeddedExpression()
+ private void ParseEmbeddedExpression(in SyntaxListBuilder builder)
{
// First, verify the type of the block
- Assert(CSharpTokenType.Transition);
+ Assert(SyntaxKind.Transition);
var transition = CurrentToken;
NextToken();
- if (At(CSharpTokenType.Transition))
+ if (At(SyntaxKind.Transition))
{
// Escaped "@"
- Output(SpanKindInternal.Code);
+ builder.Add(OutputTokensAsStatementLiteral());
// Output "@" as hidden span
Accept(transition);
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.Code);
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ builder.Add(OutputTokensAsEphemeralLiteral());
- Assert(CSharpTokenType.Transition);
+ Assert(SyntaxKind.Transition);
AcceptAndMoveNext();
- StandardStatement();
+ ParseStandardStatement(builder);
}
else
{
// Throw errors as necessary, but continue parsing
- if (At(CSharpTokenType.LeftBrace))
+ if (At(SyntaxKind.LeftBrace))
{
Context.ErrorSink.OnError(
RazorDiagnosticFactory.CreateParsing_UnexpectedNestedCodeBlock(
@@ -1416,60 +676,82 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
PutBack(transition);
// Before exiting, add a marker span if necessary
- AddMarkerTokenIfNecessary();
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
- NestedBlock();
+ var nestedBlock = ParseNestedBlock();
+ builder.Add(nestedBlock);
}
}
- private void StandardStatement()
+ private RazorSyntaxNode ParseNestedBlock()
+ {
+ var wasNested = IsNested;
+ IsNested = true;
+
+ RazorSyntaxNode nestedBlock;
+ using (PushSpanContextConfig())
+ {
+ nestedBlock = ParseBlock();
+ }
+
+ InitializeContext(SpanContext);
+ IsNested = wasNested;
+ NextToken();
+
+ return nestedBlock;
+ }
+
+ private void ParseStandardStatement(in SyntaxListBuilder builder)
{
while (!EndOfFile)
{
var bookmark = CurrentStart.AbsoluteIndex;
var read = ReadWhile(token =>
- token.Type != CSharpTokenType.Semicolon &&
- token.Type != CSharpTokenType.RazorCommentTransition &&
- token.Type != CSharpTokenType.Transition &&
- token.Type != CSharpTokenType.LeftBrace &&
- token.Type != CSharpTokenType.LeftParenthesis &&
- token.Type != CSharpTokenType.LeftBracket &&
- token.Type != CSharpTokenType.RightBrace);
+ token.Kind != SyntaxKind.Semicolon &&
+ token.Kind != SyntaxKind.RazorCommentTransition &&
+ token.Kind != SyntaxKind.Transition &&
+ token.Kind != SyntaxKind.LeftBrace &&
+ token.Kind != SyntaxKind.LeftParenthesis &&
+ token.Kind != SyntaxKind.LeftBracket &&
+ token.Kind != SyntaxKind.RightBrace);
- if (At(CSharpTokenType.LeftBrace) ||
- At(CSharpTokenType.LeftParenthesis) ||
- At(CSharpTokenType.LeftBracket))
+ if (At(SyntaxKind.LeftBrace) ||
+ At(SyntaxKind.LeftParenthesis) ||
+ At(SyntaxKind.LeftBracket))
{
Accept(read);
- if (Balance(BalancingModes.AllowCommentsAndTemplates | BalancingModes.BacktrackOnFailure))
+ if (Balance(builder, BalancingModes.AllowCommentsAndTemplates | BalancingModes.BacktrackOnFailure))
{
- Optional(CSharpTokenType.RightBrace);
+ TryAccept(SyntaxKind.RightBrace);
}
else
{
// Recovery
- AcceptUntil(CSharpTokenType.LessThan, CSharpTokenType.RightBrace);
+ AcceptUntil(SyntaxKind.LessThan, SyntaxKind.RightBrace);
return;
}
}
- else if (At(CSharpTokenType.Transition) && (NextIs(CSharpTokenType.LessThan, CSharpTokenType.Colon)))
+ else if (At(SyntaxKind.Transition) && (NextIs(SyntaxKind.LessThan, SyntaxKind.Colon)))
{
Accept(read);
- Output(SpanKindInternal.Code);
- Template();
+ builder.Add(OutputTokensAsStatementLiteral());
+ ParseTemplate(builder);
}
- else if (At(CSharpTokenType.RazorCommentTransition))
+ else if (At(SyntaxKind.RazorCommentTransition))
{
Accept(read);
- RazorComment();
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
+ builder.Add(ParseRazorComment());
}
- else if (At(CSharpTokenType.Semicolon))
+ else if (At(SyntaxKind.Semicolon))
{
Accept(read);
AcceptAndMoveNext();
return;
}
- else if (At(CSharpTokenType.RightBrace))
+ else if (At(SyntaxKind.RightBrace))
{
Accept(read);
return;
@@ -1478,110 +760,56 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
Context.Source.Position = bookmark;
NextToken();
- AcceptUntil(CSharpTokenType.LessThan, CSharpTokenType.LeftBrace, CSharpTokenType.RightBrace);
+ AcceptUntil(SyntaxKind.LessThan, SyntaxKind.LeftBrace, SyntaxKind.RightBrace);
return;
}
}
}
- private void CodeBlock(Block block)
+ private void ParseTemplate(in SyntaxListBuilder builder)
{
- CodeBlock(true, block);
- }
-
- private void CodeBlock(bool acceptTerminatingBrace, Block block)
- {
- EnsureCurrent();
- while (!EndOfFile && !At(CSharpTokenType.RightBrace))
- {
- // Parse a statement, then return here
- Statement();
- EnsureCurrent();
- }
-
- if (EndOfFile)
+ if (Context.InTemplateContext)
{
Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
- new SourceSpan(block.Start, contentLength: 1 /* { OR } */), block.Name, "}", "{"));
+ RazorDiagnosticFactory.CreateParsing_InlineMarkupBlocksCannotBeNested(
+ new SourceSpan(CurrentStart, contentLength: 1 /* @ */)));
}
- else if (acceptTerminatingBrace)
+ if (SpanContext.ChunkGenerator is ExpressionChunkGenerator)
{
- Assert(CSharpTokenType.RightBrace);
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- AcceptAndMoveNext();
- }
- }
-
- private void HandleKeyword(bool topLevel, Action fallback)
- {
- Debug.Assert(CurrentToken.Type == CSharpTokenType.Keyword && CurrentToken.Keyword != null);
- if (_keywordParsers.TryGetValue(CurrentToken.Keyword.Value, out var handler))
- {
- handler(topLevel);
+ builder.Add(OutputTokensAsExpressionLiteral());
}
else
{
- fallback();
+ builder.Add(OutputTokensAsStatementLiteral());
}
- }
- private IEnumerable SkipToNextImportantToken()
- {
- while (!EndOfFile)
+ using (var pooledResult = Pool.Allocate())
{
- var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- if (At(CSharpTokenType.RazorCommentTransition))
- {
- Accept(whitespace);
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
- RazorComment();
- }
- else
- {
- return whitespace;
- }
+ var templateBuilder = pooledResult.Builder;
+ Context.InTemplateContext = true;
+ PutCurrentBack();
+ OtherParserBlock(templateBuilder);
+
+ var template = SyntaxFactory.CSharpTemplateBlock(templateBuilder.ToList());
+ builder.Add(template);
+
+ Context.InTemplateContext = false;
}
- return Enumerable.Empty();
}
- // Common code for Parsers, but FxCop REALLY doesn't like it in the base class.. moving it here for now.
- protected override void OutputSpanBeforeRazorComment()
+ protected bool TryParseDirective(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, string directive)
{
- AddMarkerTokenIfNecessary();
- Output(SpanKindInternal.Code);
- }
-
- private void SetUpExpressions()
- {
- MapExpressionKeyword(AwaitExpression, CSharpKeyword.Await);
- }
-
- private void AwaitExpression(bool topLevel)
- {
- // Ensure that we're on the await statement (only runs in debug)
- Assert(CSharpKeyword.Await);
-
- // Accept the "await" and move on
- AcceptAndMoveNext();
-
- // Accept 1 or more spaces between the await and the following code.
- AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
-
- // Top level basically indicates if we're within an expression or statement.
- // Ex: topLevel true = @await Foo() | topLevel false = @{ await Foo(); }
- // Note that in this case @{ @await Foo() } top level is true for await.
- // Therefore, if we're top level then we want to act like an implicit expression,
- // otherwise just act as whatever we're contained in.
- if (topLevel)
+ if (_directiveParserMap.TryGetValue(directive, out var handler))
{
- // Setup the Span to be an async implicit expression (an implicit expresison that allows spaces).
- // Spaces are allowed because of "@await Foo()".
- AsyncImplicitExpression();
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ handler(builder, transition);
+ return true;
}
+
+ return false;
}
- private void SetupDirectives(IEnumerable directiveDescriptors)
+ private void SetupDirectiveParsers(IEnumerable directiveDescriptors)
{
var allDirectives = directiveDescriptors.Concat(DefaultDirectiveDescriptors).ToList();
@@ -1589,12 +817,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
var directiveDescriptor = allDirectives[i];
CurrentKeywords.Add(directiveDescriptor.Directive);
- MapDirectives(() => HandleDirective(directiveDescriptor), directiveDescriptor.Directive);
+ MapDirectives((builder, transition) => ParseExtensibleDirective(builder, transition, directiveDescriptor), directiveDescriptor.Directive);
}
- MapDirectives(TagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword);
- MapDirectives(AddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword);
- MapDirectives(RemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword);
+ MapDirectives(ParseTagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword);
+ MapDirectives(ParseAddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword);
+ MapDirectives(ParseRemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword);
}
private void EnsureDirectiveIsAtStartOfLine()
@@ -1620,340 +848,208 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
}
- private void HandleDirective(DirectiveDescriptor descriptor)
+ protected void MapDirectives(Action, CSharpTransitionSyntax> handler, params string[] directives)
{
- AssertDirective(descriptor.Directive);
-
- var directiveErrorSink = new ErrorSink();
- var savedErrorSink = Context.ErrorSink;
- Context.ErrorSink = directiveErrorSink;
-
- var directiveChunkGenerator = new DirectiveChunkGenerator(descriptor);
- try
+ foreach (var directive in directives)
{
- EnsureDirectiveIsAtStartOfLine();
-
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Directive;
- Context.Builder.CurrentBlock.ChunkGenerator = directiveChunkGenerator;
-
- AcceptAndMoveNext();
- Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
-
- // Even if an error was logged do not bail out early. If a directive was used incorrectly it doesn't mean it can't be parsed.
- ValidateDirectiveUsage(descriptor);
-
- for (var i = 0; i < descriptor.Tokens.Count; i++)
+ _directiveParserMap.Add(directive, (builder, transition) =>
{
- if (!At(CSharpTokenType.WhiteSpace) &&
- !At(CSharpTokenType.NewLine) &&
- !EndOfFile)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_DirectiveTokensMustBeSeparatedByWhitespace(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
- return;
- }
+ handler(builder, transition);
+ Context.SeenDirectives.Add(directive);
+ });
- var tokenDescriptor = descriptor.Tokens[i];
- AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ Keywords.Add(directive);
- if (tokenDescriptor.Kind == DirectiveTokenKind.Member ||
- tokenDescriptor.Kind == DirectiveTokenKind.Namespace ||
- tokenDescriptor.Kind == DirectiveTokenKind.Type)
- {
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.Code, AcceptedCharactersInternal.WhiteSpace);
-
- if (EndOfFile || At(CSharpTokenType.NewLine))
- {
- // Add a marker token to provide CSharp intellisense when we start typing the directive token.
- AddMarkerTokenIfNecessary();
- Span.ChunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor);
- Span.EditHandler = new DirectiveTokenEditHandler(Language.TokenizeString);
- Output(SpanKindInternal.Code, AcceptedCharactersInternal.NonWhiteSpace);
- }
- }
- else
- {
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.Markup, AcceptedCharactersInternal.WhiteSpace);
- }
-
- if (tokenDescriptor.Optional && (EndOfFile || At(CSharpTokenType.NewLine)))
- {
- break;
- }
- else if (EndOfFile)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_UnexpectedEOFAfterDirective(
- new SourceSpan(CurrentStart, contentLength: 1),
- descriptor.Directive,
- tokenDescriptor.Kind.ToString().ToLowerInvariant()));
- return;
- }
-
- switch (tokenDescriptor.Kind)
- {
- case DirectiveTokenKind.Type:
- if (!NamespaceOrTypeName())
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_DirectiveExpectsTypeName(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
-
- return;
- }
- break;
-
- case DirectiveTokenKind.Namespace:
- if (!QualifiedIdentifier(out var identifierLength))
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_DirectiveExpectsNamespace(
- new SourceSpan(CurrentStart, identifierLength), descriptor.Directive));
-
- return;
- }
- break;
-
- case DirectiveTokenKind.Member:
- if (At(CSharpTokenType.Identifier))
- {
- AcceptAndMoveNext();
- }
- else
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_DirectiveExpectsIdentifier(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
- return;
- }
- break;
-
- case DirectiveTokenKind.String:
- if (At(CSharpTokenType.StringLiteral) && CurrentToken.Errors.Count == 0)
- {
- AcceptAndMoveNext();
- }
- else
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_DirectiveExpectsQuotedStringLiteral(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
- return;
- }
- break;
- }
-
- Span.ChunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor);
- Span.EditHandler = new DirectiveTokenEditHandler(Language.TokenizeString);
- Output(SpanKindInternal.Code, AcceptedCharactersInternal.NonWhiteSpace);
+ // These C# keywords are reserved for use in directives. It's an error to use them outside of
+ // a directive. This code removes the error generation if the directive *is* registered.
+ if (string.Equals(directive, "class", StringComparison.OrdinalIgnoreCase))
+ {
+ _keywordParserMap.Remove(CSharpKeyword.Class);
}
-
- AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
- Span.ChunkGenerator = SpanChunkGenerator.Null;
-
- switch (descriptor.Kind)
+ else if (string.Equals(directive, "namespace", StringComparison.OrdinalIgnoreCase))
{
- case DirectiveKind.SingleLine:
- Output(SpanKindInternal.None, AcceptedCharactersInternal.WhiteSpace);
-
- Optional(CSharpTokenType.Semicolon);
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.WhiteSpace);
-
- AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
-
- if (At(CSharpTokenType.NewLine))
- {
- AcceptAndMoveNext();
- }
- else if (!EndOfFile)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length),
- descriptor.Directive,
- Resources.ErrorComponent_Newline));
- }
-
- Span.ChunkGenerator = SpanChunkGenerator.Null;
-
- // This should contain the optional whitespace after the optional semicolon and the new line.
- // Output as Markup as we want intellisense here.
- Output(SpanKindInternal.Markup, AcceptedCharactersInternal.WhiteSpace);
- break;
- case DirectiveKind.RazorBlock:
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- Output(SpanKindInternal.Markup, AcceptedCharactersInternal.AllWhiteSpace);
-
- ParseDirectiveBlock(descriptor, parseChildren: (startingBraceLocation) =>
- {
- // When transitioning to the HTML parser we no longer want to act as if we're in a nested C# state.
- // For instance, if @hello.
is in a nested C# block we don't want the trailing '.' to be handled
- // as C#; it should be handled as a period because it's wrapped in markup.
- var wasNested = IsNested;
- IsNested = false;
-
- using (PushSpanConfig())
- {
- HtmlParser.ParseRazorBlock(Tuple.Create("{", "}"), caseSensitive: true);
- }
-
- Span.Start = CurrentLocation;
- Initialize(Span);
-
- IsNested = wasNested;
-
- NextToken();
- });
- break;
- case DirectiveKind.CodeBlock:
- AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
- Output(SpanKindInternal.Markup, AcceptedCharactersInternal.AllWhiteSpace);
-
- ParseDirectiveBlock(descriptor, parseChildren: (startingBraceLocation) =>
- {
- NextToken();
- Balance(BalancingModes.NoErrorOnFailure, CSharpTokenType.LeftBrace, CSharpTokenType.RightBrace, startingBraceLocation);
- Span.ChunkGenerator = new StatementChunkGenerator();
- var existingEditHandler = Span.EditHandler;
- Span.EditHandler = new CodeBlockEditHandler(Language.TokenizeString);
-
- AddMarkerTokenIfNecessary();
-
- Output(SpanKindInternal.Code);
-
- Span.EditHandler = existingEditHandler;
- });
- break;
- }
- }
- finally
- {
- if (directiveErrorSink.Errors.Count > 0)
- {
- directiveChunkGenerator.Diagnostics.AddRange(directiveErrorSink.Errors);
- }
-
- Context.ErrorSink = savedErrorSink;
- }
- }
-
-
- private void ValidateDirectiveUsage(DirectiveDescriptor descriptor)
- {
- if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
- {
- if (Context.SeenDirectives.Contains(descriptor.Directive))
- {
- // There will always be at least 1 child because of the `@` transition.
- var directiveStart = Context.Builder.CurrentBlock.Children.First().Start;
- var errorLength = /* @ */ 1 + descriptor.Directive.Length;
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_DuplicateDirective(
- new SourceSpan(directiveStart, errorLength), descriptor.Directive));
-
- return;
+ _keywordParserMap.Remove(CSharpKeyword.Namespace);
}
}
}
- private void ParseDirectiveBlock(DirectiveDescriptor descriptor, Action parseChildren)
- {
- if (EndOfFile)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_UnexpectedEOFAfterDirective(
- new SourceSpan(CurrentStart, contentLength: 1 /* { */), descriptor.Directive, "{"));
- }
- else if (!At(CSharpTokenType.LeftBrace))
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
- new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive, "{"));
- }
- else
- {
- var editHandler = new AutoCompleteEditHandler(Language.TokenizeString, autoCompleteAtEndOfSpan: true);
- Span.EditHandler = editHandler;
- var startingBraceLocation = CurrentStart;
- Accept(CurrentToken);
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
-
- parseChildren(startingBraceLocation);
-
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- if (!Optional(CSharpTokenType.RightBrace))
- {
- editHandler.AutoCompleteString = "}";
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
- new SourceSpan(startingBraceLocation, contentLength: 1 /* } */), descriptor.Directive, "}", "{"));
- }
- else
- {
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- }
- CompleteBlock(insertMarkerIfNecessary: false, captureWhitespaceToEndOfLine: true);
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.MetaCode, AcceptedCharactersInternal.None);
- }
- }
-
- protected virtual void TagHelperPrefixDirective()
+ private void ParseTagHelperPrefixDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
{
RazorDiagnostic duplicateDiagnostic = null;
if (Context.SeenDirectives.Contains(SyntaxConstants.CSharp.TagHelperPrefixKeyword))
{
- // There wil always be at least 1 child because of the `@` transition.
- var directiveStart = Context.Builder.CurrentBlock.Children.First().Start;
+ var directiveStart = CurrentStart;
+ if (transition != null)
+ {
+ // Start the error from the Transition '@'.
+ directiveStart = new SourceLocation(
+ directiveStart.FilePath,
+ directiveStart.AbsoluteIndex - 1,
+ directiveStart.LineIndex,
+ directiveStart.CharacterIndex - 1);
+ }
var errorLength = /* @ */ 1 + SyntaxConstants.CSharp.TagHelperPrefixKeyword.Length;
duplicateDiagnostic = RazorDiagnosticFactory.CreateParsing_DuplicateDirective(
new SourceSpan(directiveStart, errorLength),
SyntaxConstants.CSharp.TagHelperPrefixKeyword);
}
- TagHelperDirective(
+ var directiveBody = ParseTagHelperDirective(
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
- (prefix, errors) =>
+ (prefix, errors, startLocation) =>
{
if (duplicateDiagnostic != null)
{
errors.Add(duplicateDiagnostic);
}
- var parsedDirective = ParseDirective(prefix, Span.Start, TagHelperDirectiveType.TagHelperPrefix, errors);
+ var parsedDirective = ParseDirective(prefix, startLocation, TagHelperDirectiveType.TagHelperPrefix, errors);
return new TagHelperPrefixDirectiveChunkGenerator(
prefix,
parsedDirective.DirectiveText,
errors);
});
+
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
}
- // Internal for testing.
- internal void ValidateTagHelperPrefix(
- string prefix,
- SourceLocation directiveLocation,
- List diagnostics)
+ private void ParseAddTagHelperDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
{
- foreach (var character in prefix)
- {
- // Prefixes are correlated with tag names, tag names cannot have whitespace.
- if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
+ var directiveBody = ParseTagHelperDirective(
+ SyntaxConstants.CSharp.AddTagHelperKeyword,
+ (lookupText, errors, startLocation) =>
{
- diagnostics.Add(
- RazorDiagnosticFactory.CreateParsing_InvalidTagHelperPrefixValue(
- new SourceSpan(directiveLocation, prefix.Length),
- SyntaxConstants.CSharp.TagHelperPrefixKeyword,
- character,
- prefix));
+ var parsedDirective = ParseDirective(lookupText, startLocation, TagHelperDirectiveType.AddTagHelper, errors);
- return;
+ return new AddTagHelperChunkGenerator(
+ lookupText,
+ parsedDirective.DirectiveText,
+ parsedDirective.TypePattern,
+ parsedDirective.AssemblyName,
+ errors);
+ });
+
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
+ }
+
+ private void ParseRemoveTagHelperDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ var directiveBody = ParseTagHelperDirective(
+ SyntaxConstants.CSharp.RemoveTagHelperKeyword,
+ (lookupText, errors, startLocation) =>
+ {
+ var parsedDirective = ParseDirective(lookupText, startLocation, TagHelperDirectiveType.RemoveTagHelper, errors);
+
+ return new RemoveTagHelperChunkGenerator(
+ lookupText,
+ parsedDirective.DirectiveText,
+ parsedDirective.TypePattern,
+ parsedDirective.AssemblyName,
+ errors);
+ });
+
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
+ }
+
+ [Conditional("DEBUG")]
+ protected void AssertDirective(string directive)
+ {
+ Debug.Assert(CurrentToken.Kind == SyntaxKind.Identifier || CurrentToken.Kind == SyntaxKind.Keyword);
+ Debug.Assert(string.Equals(CurrentToken.Content, directive, StringComparison.Ordinal));
+ }
+
+ private RazorDirectiveBodySyntax ParseTagHelperDirective(
+ string keyword,
+ Func, SourceLocation, ISpanChunkGenerator> chunkGeneratorFactory)
+ {
+ AssertDirective(keyword);
+
+ var savedErrorSink = Context.ErrorSink;
+ var directiveErrorSink = new ErrorSink();
+ RazorMetaCodeSyntax keywordBlock = null;
+ using (var pooledResult = Pool.Allocate())
+ {
+ var directiveBuilder = pooledResult.Builder;
+ Context.ErrorSink = directiveErrorSink;
+
+ string directiveValue = null;
+ SourceLocation? valueStartLocation = null;
+ try
+ {
+ EnsureDirectiveIsAtStartOfLine();
+
+ var keywordStartLocation = CurrentStart;
+
+ // Accept the directive name
+ var keywordToken = EatCurrentToken();
+ var keywordLength = keywordToken.FullWidth + 1 /* @ */;
+
+ var foundWhitespace = At(SyntaxKind.Whitespace);
+
+ // If we found whitespace then any content placed within the whitespace MAY cause a destructive change
+ // to the document. We can't accept it.
+ var acceptedCharacters = foundWhitespace ? AcceptedCharactersInternal.None : AcceptedCharactersInternal.AnyExceptNewline;
+ Accept(keywordToken);
+ keywordBlock = OutputAsMetaCode(Output(), acceptedCharacters);
+
+ AcceptWhile(SyntaxKind.Whitespace);
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = acceptedCharacters;
+ directiveBuilder.Add(OutputAsMarkupLiteral());
+
+ if (EndOfFile || At(SyntaxKind.NewLine))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveMustHaveValue(
+ new SourceSpan(keywordStartLocation, keywordLength), keyword));
+
+ directiveValue = string.Empty;
+ }
+ else
+ {
+ // Need to grab the current location before we accept until the end of the line.
+ valueStartLocation = CurrentStart;
+
+ // Parse to the end of the line. Essentially accepts anything until end of line, comments, invalid code
+ // etc.
+ AcceptUntil(SyntaxKind.NewLine);
+
+ // Pull out the value and remove whitespaces and optional quotes
+ var rawValue = string.Concat(TokenBuilder.ToList().Nodes.Select(s => s.Content)).Trim();
+
+ var startsWithQuote = rawValue.StartsWith("\"", StringComparison.Ordinal);
+ var endsWithQuote = rawValue.EndsWith("\"", StringComparison.Ordinal);
+ if (startsWithQuote != endsWithQuote)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
+ new SourceSpan(valueStartLocation.Value, rawValue.Length), keyword));
+ }
+
+ directiveValue = rawValue;
+ }
}
+ finally
+ {
+ SpanContext.ChunkGenerator = chunkGeneratorFactory(
+ directiveValue,
+ directiveErrorSink.Errors.ToList(),
+ valueStartLocation ?? CurrentStart);
+ Context.ErrorSink = savedErrorSink;
+ }
+
+ // Finish the block and output the tokens
+ CompleteBlock();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AnyExceptNewline;
+
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ var directiveCodeBlock = SyntaxFactory.CSharpCodeBlock(directiveBuilder.ToList());
+
+ return SyntaxFactory.RazorDirectiveBody(keywordBlock, directiveCodeBlock);
}
}
@@ -1982,9 +1078,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// Ex: @addTagHelper "*, Microsoft.AspNetCore.CoolLibrary"
// ^ ^
// Start End
- if (Span.Tokens.Count == 1 && (Span.Tokens[0] as CSharpToken)?.Type == CSharpTokenType.StringLiteral)
+ if (TokenBuilder.Count == 1 &&
+ TokenBuilder[0] is SyntaxToken token &&
+ token.Kind == SyntaxKind.StringLiteral)
{
- offset += Span.Tokens[0].Content.IndexOf(directiveText, StringComparison.Ordinal);
+ offset += token.Content.IndexOf(directiveText, StringComparison.Ordinal);
// This is safe because inside one of these directives all of the text needs to be on the
// same line.
@@ -2044,122 +1142,1294 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return directive;
}
- protected virtual void AddTagHelperDirective()
+ // Internal for testing.
+ internal void ValidateTagHelperPrefix(
+ string prefix,
+ SourceLocation directiveLocation,
+ List diagnostics)
{
- TagHelperDirective(
- SyntaxConstants.CSharp.AddTagHelperKeyword,
- (lookupText, errors) =>
+ foreach (var character in prefix)
+ {
+ // Prefixes are correlated with tag names, tag names cannot have whitespace.
+ if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
{
- var parsedDirective = ParseDirective(lookupText, Span.Start, TagHelperDirectiveType.AddTagHelper, errors);
+ diagnostics.Add(
+ RazorDiagnosticFactory.CreateParsing_InvalidTagHelperPrefixValue(
+ new SourceSpan(directiveLocation, prefix.Length),
+ SyntaxConstants.CSharp.TagHelperPrefixKeyword,
+ character,
+ prefix));
- return new AddTagHelperChunkGenerator(
- lookupText,
- parsedDirective.DirectiveText,
- parsedDirective.TypePattern,
- parsedDirective.AssemblyName,
- errors);
- });
+ return;
+ }
+ }
}
- protected virtual void RemoveTagHelperDirective()
+ private void ParseExtensibleDirective(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, DirectiveDescriptor descriptor)
{
- TagHelperDirective(
- SyntaxConstants.CSharp.RemoveTagHelperKeyword,
- (lookupText, errors) =>
- {
- var parsedDirective = ParseDirective(lookupText, Span.Start, TagHelperDirectiveType.RemoveTagHelper, errors);
+ AssertDirective(descriptor.Directive);
- return new RemoveTagHelperChunkGenerator(
- lookupText,
- parsedDirective.DirectiveText,
- parsedDirective.TypePattern,
- parsedDirective.AssemblyName,
- errors);
- });
- }
-
- [Conditional("DEBUG")]
- protected void AssertDirective(string directive)
- {
- Debug.Assert(CurrentToken.Type == CSharpTokenType.Identifier || CurrentToken.Type == CSharpTokenType.Keyword);
- Debug.Assert(string.Equals(CurrentToken.Content, directive, StringComparison.Ordinal));
- }
-
- private void TagHelperDirective(string keyword, Func, ISpanChunkGenerator> chunkGeneratorFactory)
- {
- AssertDirective(keyword);
-
- var savedErrorSink = Context.ErrorSink;
var directiveErrorSink = new ErrorSink();
+ var savedErrorSink = Context.ErrorSink;
Context.ErrorSink = directiveErrorSink;
- string directiveValue = null;
- try
+ using (var pooledResult = Pool.Allocate())
{
- EnsureDirectiveIsAtStartOfLine();
+ var directiveBuilder = pooledResult.Builder;
+ RazorMetaCodeSyntax keywordBlock = null;
- var keywordStartLocation = CurrentStart;
-
- // Accept the directive name
- AcceptAndMoveNext();
-
- // Set the block type
- Context.Builder.CurrentBlock.Type = BlockKindInternal.Directive;
-
- var keywordLength = Span.End.AbsoluteIndex - Span.Start.AbsoluteIndex;
-
- var foundWhitespace = At(CSharpTokenType.WhiteSpace);
-
- // If we found whitespace then any content placed within the whitespace MAY cause a destructive change
- // to the document. We can't accept it.
- var acceptedCharacters = foundWhitespace ? AcceptedCharactersInternal.None : AcceptedCharactersInternal.AnyExceptNewline;
- Output(SpanKindInternal.MetaCode, acceptedCharacters);
-
- AcceptWhile(CSharpTokenType.WhiteSpace);
- Span.ChunkGenerator = SpanChunkGenerator.Null;
- Output(SpanKindInternal.Markup, acceptedCharacters);
-
- if (EndOfFile || At(CSharpTokenType.NewLine))
+ try
{
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_DirectiveMustHaveValue(
- new SourceSpan(keywordStartLocation, keywordLength), keyword));
+ EnsureDirectiveIsAtStartOfLine();
+ var directiveStart = CurrentStart;
+ if (transition != null)
+ {
+ // Start the error from the Transition '@'.
+ directiveStart = new SourceLocation(
+ directiveStart.FilePath,
+ directiveStart.AbsoluteIndex - 1,
+ directiveStart.LineIndex,
+ directiveStart.CharacterIndex - 1);
+ }
- directiveValue = string.Empty;
+ AcceptAndMoveNext();
+ keywordBlock = OutputAsMetaCode(Output());
+
+ // Even if an error was logged do not bail out early. If a directive was used incorrectly it doesn't mean it can't be parsed.
+ ValidateDirectiveUsage(descriptor, directiveStart);
+
+ for (var i = 0; i < descriptor.Tokens.Count; i++)
+ {
+ if (!At(SyntaxKind.Whitespace) &&
+ !At(SyntaxKind.NewLine) &&
+ !EndOfFile)
+ {
+ // This case should never happen in a real scenario. We're just being defensive.
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveTokensMustBeSeparatedByWhitespace(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+
+ builder.Add(BuildDirective());
+ return;
+ }
+
+ var tokenDescriptor = descriptor.Tokens[i];
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ if (tokenDescriptor.Kind == DirectiveTokenKind.Member ||
+ tokenDescriptor.Kind == DirectiveTokenKind.Namespace ||
+ tokenDescriptor.Kind == DirectiveTokenKind.Type)
+ {
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+
+ if (EndOfFile || At(SyntaxKind.NewLine))
+ {
+ // Add a marker token to provide CSharp intellisense when we start typing the directive token.
+ AcceptMarkerTokenIfNecessary();
+ SpanContext.ChunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor);
+ SpanContext.EditHandler = new DirectiveTokenEditHandler(Language.TokenizeString);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace;
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ }
+ }
+ else
+ {
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputAsMarkupEphemeralLiteral());
+ }
+
+ if (tokenDescriptor.Optional && (EndOfFile || At(SyntaxKind.NewLine)))
+ {
+ break;
+ }
+ else if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedEOFAfterDirective(
+ new SourceSpan(CurrentStart, contentLength: 1),
+ descriptor.Directive,
+ tokenDescriptor.Kind.ToString().ToLowerInvariant()));
+ builder.Add(BuildDirective());
+ return;
+ }
+
+ switch (tokenDescriptor.Kind)
+ {
+ case DirectiveTokenKind.Type:
+ if (!TryParseNamespaceOrTypeName(directiveBuilder))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsTypeName(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+
+ case DirectiveTokenKind.Namespace:
+ if (!TryParseQualifiedIdentifier(out var identifierLength))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsNamespace(
+ new SourceSpan(CurrentStart, identifierLength), descriptor.Directive));
+
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+
+ case DirectiveTokenKind.Member:
+ if (At(SyntaxKind.Identifier))
+ {
+ AcceptAndMoveNext();
+ }
+ else
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsIdentifier(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+
+ case DirectiveTokenKind.String:
+ if (At(SyntaxKind.StringLiteral) && !CurrentToken.ContainsDiagnostics)
+ {
+ AcceptAndMoveNext();
+ }
+ else
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DirectiveExpectsQuotedStringLiteral(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive));
+ builder.Add(BuildDirective());
+ return;
+ }
+ break;
+ }
+
+ SpanContext.ChunkGenerator = new DirectiveTokenChunkGenerator(tokenDescriptor);
+ SpanContext.EditHandler = new DirectiveTokenEditHandler(Language.TokenizeString);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace;
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ }
+
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+
+ switch (descriptor.Kind)
+ {
+ case DirectiveKind.SingleLine:
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputTokensAsUnclassifiedLiteral());
+
+ TryAccept(SyntaxKind.Semicolon);
+ directiveBuilder.Add(OutputAsMetaCode(Output(), AcceptedCharactersInternal.Whitespace));
+
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ if (At(SyntaxKind.NewLine))
+ {
+ AcceptAndMoveNext();
+ }
+ else if (!EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length),
+ descriptor.Directive,
+ Resources.ErrorComponent_Newline));
+ }
+
+
+ // This should contain the optional whitespace after the optional semicolon and the new line.
+ // Output as Markup as we want intellisense here.
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Whitespace;
+ directiveBuilder.Add(OutputAsMarkupEphemeralLiteral());
+ break;
+ case DirectiveKind.RazorBlock:
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AllWhitespace;
+ directiveBuilder.Add(OutputAsMarkupLiteral());
+
+ ParseDirectiveBlock(directiveBuilder, descriptor, parseChildren: (childBuilder, startingBraceLocation) =>
+ {
+ // When transitioning to the HTML parser we no longer want to act as if we're in a nested C# state.
+ // For instance, if @hello.
is in a nested C# block we don't want the trailing '.' to be handled
+ // as C#; it should be handled as a period because it's wrapped in markup.
+ var wasNested = IsNested;
+ IsNested = false;
+
+ using (PushSpanContextConfig())
+ {
+ var razorBlock = HtmlParser.ParseRazorBlock(Tuple.Create("{", "}"), caseSensitive: true);
+ directiveBuilder.Add(razorBlock);
+ }
+
+ InitializeContext(SpanContext);
+ IsNested = wasNested;
+ NextToken();
+ });
+ break;
+ case DirectiveKind.CodeBlock:
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AllWhitespace;
+ directiveBuilder.Add(OutputAsMarkupLiteral());
+
+ ParseDirectiveBlock(directiveBuilder, descriptor, parseChildren: (childBuilder, startingBraceLocation) =>
+ {
+ NextToken();
+ Balance(childBuilder, BalancingModes.NoErrorOnFailure, SyntaxKind.LeftBrace, SyntaxKind.RightBrace, startingBraceLocation);
+ SpanContext.ChunkGenerator = new StatementChunkGenerator();
+ var existingEditHandler = SpanContext.EditHandler;
+ SpanContext.EditHandler = new CodeBlockEditHandler(Language.TokenizeString);
+
+ AcceptMarkerTokenIfNecessary();
+
+ childBuilder.Add(OutputTokensAsStatementLiteral());
+
+ SpanContext.EditHandler = existingEditHandler;
+ });
+ break;
+ }
+ }
+ finally
+ {
+ Context.ErrorSink = savedErrorSink;
+ }
+
+ builder.Add(BuildDirective());
+
+ RazorDirectiveSyntax BuildDirective()
+ {
+ directiveBuilder.Add(OutputTokensAsStatementLiteral());
+ var directiveCodeBlock = SyntaxFactory.CSharpCodeBlock(directiveBuilder.ToList());
+
+ var directiveBody = SyntaxFactory.RazorDirectiveBody(keywordBlock, directiveCodeBlock);
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ directive = (RazorDirectiveSyntax)directive.SetDiagnostics(directiveErrorSink.Errors.ToArray());
+ directive = directive.WithDirectiveDescriptor(descriptor);
+ return directive;
+ }
+ }
+ }
+
+ private void ValidateDirectiveUsage(DirectiveDescriptor descriptor, SourceLocation directiveStart)
+ {
+ if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring)
+ {
+ if (Context.SeenDirectives.Contains(descriptor.Directive))
+ {
+ // There will always be at least 1 child because of the `@` transition.
+ var errorLength = /* @ */ 1 + descriptor.Directive.Length;
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_DuplicateDirective(
+ new SourceSpan(directiveStart, errorLength), descriptor.Directive));
+
+ return;
+ }
+ }
+ }
+
+ // Used for parsing a qualified name like that which follows the `namespace` keyword.
+ //
+ // qualified-identifier:
+ // identifier
+ // qualified-identifier . identifier
+ protected bool TryParseQualifiedIdentifier(out int identifierLength)
+ {
+ var currentIdentifierLength = 0;
+ var expectingDot = false;
+ var tokens = ReadWhile(token =>
+ {
+ var type = token.Kind;
+ if ((expectingDot && type == SyntaxKind.Dot) ||
+ (!expectingDot && type == SyntaxKind.Identifier))
+ {
+ expectingDot = !expectingDot;
+ return true;
+ }
+
+ if (type != SyntaxKind.Whitespace &&
+ type != SyntaxKind.NewLine)
+ {
+ expectingDot = false;
+ currentIdentifierLength += token.Content.Length;
+ }
+
+ return false;
+ });
+
+ identifierLength = currentIdentifierLength;
+ var validQualifiedIdentifier = expectingDot;
+ if (validQualifiedIdentifier)
+ {
+ foreach (var token in tokens)
+ {
+ identifierLength += token.Content.Length;
+ Accept(token);
+ }
+
+ return true;
+ }
+ else
+ {
+ PutCurrentBack();
+
+ foreach (var token in tokens)
+ {
+ identifierLength += token.Content.Length;
+ PutBack(token);
+ }
+
+ EnsureCurrent();
+ return false;
+ }
+ }
+
+ private void ParseDirectiveBlock(in SyntaxListBuilder builder, DirectiveDescriptor descriptor, Action, SourceLocation> parseChildren)
+ {
+ if (EndOfFile)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedEOFAfterDirective(
+ new SourceSpan(CurrentStart, contentLength: 1 /* { */), descriptor.Directive, "{"));
+ }
+ else if (!At(SyntaxKind.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_UnexpectedDirectiveLiteral(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), descriptor.Directive, "{"));
+ }
+ else
+ {
+ var editHandler = new AutoCompleteEditHandler(Language.TokenizeString, autoCompleteAtEndOfSpan: true);
+ SpanContext.EditHandler = editHandler;
+ var startingBraceLocation = CurrentStart;
+ Accept(CurrentToken);
+ builder.Add(OutputAsMetaCode(Output()));
+
+ using (var pooledResult = Pool.Allocate())
+ {
+ var childBuilder = pooledResult.Builder;
+ parseChildren(childBuilder, startingBraceLocation);
+ if (childBuilder.Count > 0)
+ {
+ builder.Add(SyntaxFactory.CSharpCodeBlock(childBuilder.ToList()));
+ }
+ }
+
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
+ if (!TryAccept(SyntaxKind.RightBrace))
+ {
+ editHandler.AutoCompleteString = "}";
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
+ new SourceSpan(startingBraceLocation, contentLength: 1 /* } */), descriptor.Directive, "}", "{"));
+
+ Accept(SyntaxFactory.MissingToken(SyntaxKind.RightBrace));
}
else
{
- // Need to grab the current location before we accept until the end of the line.
- var startLocation = CurrentStart;
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ }
+ CompleteBlock(insertMarkerIfNecessary: false, captureWhitespaceToEndOfLine: true);
+ builder.Add(OutputAsMetaCode(Output(), SpanContext.EditHandler.AcceptedCharacters));
+ }
+ }
- // Parse to the end of the line. Essentially accepts anything until end of line, comments, invalid code
- // etc.
- AcceptUntil(CSharpTokenType.NewLine);
+ private bool TryParseKeyword(in SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
+ Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword && result.HasValue);
+ if (_keywordParserMap.TryGetValue(result.Value, out var handler))
+ {
+ handler(builder, transition);
+ return true;
+ }
- // Pull out the value and remove whitespaces and optional quotes
- var rawValue = string.Concat(Span.Tokens.Select(s => s.Content)).Trim();
+ return false;
+ }
- var startsWithQuote = rawValue.StartsWith("\"", StringComparison.Ordinal);
- var endsWithQuote = rawValue.EndsWith("\"", StringComparison.Ordinal);
- if (startsWithQuote != endsWithQuote)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_IncompleteQuotesAroundDirective(
- new SourceSpan(startLocation, rawValue.Length), keyword));
- }
+ private void SetupExpressionParsers()
+ {
+ MapExpressionKeyword(ParseAwaitExpression, CSharpKeyword.Await);
+ }
- directiveValue = rawValue;
+ private void SetupKeywordParsers()
+ {
+ MapKeywords(
+ ParseConditionalBlock,
+ CSharpKeyword.For,
+ CSharpKeyword.Foreach,
+ CSharpKeyword.While,
+ CSharpKeyword.Switch,
+ CSharpKeyword.Lock);
+ MapKeywords(ParseCaseStatement, false, CSharpKeyword.Case, CSharpKeyword.Default);
+ MapKeywords(ParseIfStatement, CSharpKeyword.If);
+ MapKeywords(ParseTryStatement, CSharpKeyword.Try);
+ MapKeywords(ParseDoStatement, CSharpKeyword.Do);
+ MapKeywords(ParseUsingKeyword, CSharpKeyword.Using);
+ MapKeywords(ParseReservedDirective, CSharpKeyword.Class, CSharpKeyword.Namespace);
+ }
+
+ private void MapExpressionKeyword(Action, CSharpTransitionSyntax> handler, CSharpKeyword keyword)
+ {
+ _keywordParserMap.Add(keyword, handler);
+
+ // Expression keywords don't belong in the regular keyword list
+ }
+
+ private void MapKeywords(Action, CSharpTransitionSyntax> handler, params CSharpKeyword[] keywords)
+ {
+ MapKeywords(handler, topLevel: true, keywords: keywords);
+ }
+
+ private void MapKeywords(Action, CSharpTransitionSyntax> handler, bool topLevel, params CSharpKeyword[] keywords)
+ {
+ foreach (var keyword in keywords)
+ {
+ _keywordParserMap.Add(keyword, handler);
+ if (topLevel)
+ {
+ Keywords.Add(CSharpLanguageCharacteristics.GetKeyword(keyword));
}
}
- finally
+ }
+
+ private void ParseAwaitExpression(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ // Ensure that we're on the await statement (only runs in debug)
+ Assert(CSharpKeyword.Await);
+
+ // Accept the "await" and move on
+ AcceptAndMoveNext();
+
+ // Accept 1 or more spaces between the await and the following code.
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ // Top level basically indicates if we're within an expression or statement.
+ // Ex: topLevel true = @await Foo() | topLevel false = @{ await Foo(); }
+ // Note that in this case @{ @await Foo() } top level is true for await.
+ // Therefore, if we're top level then we want to act like an implicit expression,
+ // otherwise just act as whatever we're contained in.
+ var topLevel = transition != null;
+ if (topLevel)
{
- Span.ChunkGenerator = chunkGeneratorFactory(directiveValue, directiveErrorSink.Errors.ToList());
- Context.ErrorSink = savedErrorSink;
+ // Setup the Span to be an async implicit expression (an implicit expresison that allows spaces).
+ // Spaces are allowed because of "@await Foo()".
+ var implicitExpressionBody = ParseImplicitExpressionBody(async: true);
+ builder.Add(SyntaxFactory.CSharpImplicitExpression(transition, implicitExpressionBody));
+ }
+ }
+
+ private void ParseConditionalBlock(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ var topLevel = transition != null;
+ ParseConditionalBlock(builder, transition, topLevel);
+ }
+
+ private void ParseConditionalBlock(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, bool topLevel)
+ {
+ Assert(SyntaxKind.Keyword);
+ if (transition != null)
+ {
+ builder.Add(transition);
}
- // Output the span and finish the block
+ var block = new Block(CurrentToken, CurrentStart);
+ ParseConditionalBlock(builder, block);
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseConditionalBlock(in SyntaxListBuilder builder, Block block)
+ {
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // Parse the condition, if present (if not present, we'll let the C# compiler complain)
+ if (TryParseCondition(builder))
+ {
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private bool TryParseCondition(in SyntaxListBuilder builder)
+ {
+ if (At(SyntaxKind.LeftParenthesis))
+ {
+ var complete = Balance(builder, BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
+ if (!complete)
+ {
+ AcceptUntil(SyntaxKind.NewLine);
+ }
+ else
+ {
+ TryAccept(SyntaxKind.RightParenthesis);
+ }
+ return complete;
+ }
+ return true;
+ }
+
+ private void ParseExpectedCodeBlock(in SyntaxListBuilder builder, Block block)
+ {
+ if (!EndOfFile)
+ {
+ // Check for "{" to make sure we're at a block
+ if (!At(SyntaxKind.LeftBrace))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_SingleLineControlFlowStatementsNotAllowed(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length),
+ Language.GetSample(SyntaxKind.LeftBrace),
+ CurrentToken.Content));
+ }
+
+ // Parse the statement and then we're done
+ ParseStatement(builder, block);
+ }
+ }
+
+ private void ParseUnconditionalBlock(in SyntaxListBuilder builder)
+ {
+ Assert(SyntaxKind.Keyword);
+ var block = new Block(CurrentToken, CurrentStart);
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ ParseExpectedCodeBlock(builder, block);
+ }
+
+ private void ParseCaseStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(SyntaxKind.Keyword);
+ if (transition != null)
+ {
+ // Normally, case statement won't start with a transition in a valid scenario.
+ // If it does, just accept it and let the compiler complain.
+ builder.Add(transition);
+ }
+ var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
+ Debug.Assert(result.HasValue &&
+ (result.Value == CSharpKeyword.Case ||
+ result.Value == CSharpKeyword.Default));
+ AcceptUntil(SyntaxKind.Colon);
+ TryAccept(SyntaxKind.Colon);
+ }
+
+ private void ParseIfStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.If);
+ ParseConditionalBlock(builder, transition, topLevel: false);
+ ParseAfterIfClause(builder);
+ var topLevel = transition != null;
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseAfterIfClause(SyntaxListBuilder builder)
+ {
+ // Grab whitespace and razor comments
+ var whitespace = SkipToNextImportantToken(builder);
+
+ // Check for an else part
+ if (At(CSharpKeyword.Else))
+ {
+ Accept(whitespace);
+ Assert(CSharpKeyword.Else);
+ ParseElseClause(builder);
+ }
+ else
+ {
+ // No else, return whitespace
+ PutCurrentBack();
+ PutBack(whitespace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ }
+ }
+
+ private void ParseElseClause(in SyntaxListBuilder builder)
+ {
+ if (!At(CSharpKeyword.Else))
+ {
+ return;
+ }
+ var block = new Block(CurrentToken, CurrentStart);
+
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(CSharpKeyword.If))
+ {
+ // ElseIf
+ block.Name = SyntaxConstants.CSharp.ElseIfKeyword;
+ ParseConditionalBlock(builder, block);
+ ParseAfterIfClause(builder);
+ }
+ else if (!EndOfFile)
+ {
+ // Else
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private void ParseTryStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.Try);
+ var topLevel = transition != null;
+ if (topLevel)
+ {
+ builder.Add(transition);
+ }
+
+ ParseUnconditionalBlock(builder);
+ ParseAfterTryClause(builder);
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseAfterTryClause(in SyntaxListBuilder builder)
+ {
+ // Grab whitespace
+ var whitespace = SkipToNextImportantToken(builder);
+
+ // Check for a catch or finally part
+ if (At(CSharpKeyword.Catch))
+ {
+ Accept(whitespace);
+ Assert(CSharpKeyword.Catch);
+ ParseFilterableCatchBlock(builder);
+ ParseAfterTryClause(builder);
+ }
+ else if (At(CSharpKeyword.Finally))
+ {
+ Accept(whitespace);
+ Assert(CSharpKeyword.Finally);
+ ParseUnconditionalBlock(builder);
+ }
+ else
+ {
+ // Return whitespace and end the block
+ PutCurrentBack();
+ PutBack(whitespace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ }
+ }
+
+ private void ParseFilterableCatchBlock(in SyntaxListBuilder builder)
+ {
+ Assert(CSharpKeyword.Catch);
+
+ var block = new Block(CurrentToken, CurrentStart);
+
+ // Accept "catch"
+ AcceptAndMoveNext();
+ AcceptWhile(IsValidStatementSpacingToken);
+
+ // Parse the catch condition if present. If not present, let the C# compiler complain.
+ if (TryParseCondition(builder))
+ {
+ AcceptWhile(IsValidStatementSpacingToken);
+
+ if (At(CSharpKeyword.When))
+ {
+ // Accept "when".
+ AcceptAndMoveNext();
+ AcceptWhile(IsValidStatementSpacingToken);
+
+ // Parse the filter condition if present. If not present, let the C# compiler complain.
+ if (!TryParseCondition(builder))
+ {
+ // Incomplete condition.
+ return;
+ }
+
+ AcceptWhile(IsValidStatementSpacingToken);
+ }
+
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private void ParseDoStatement(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.Do);
+ if (transition != null)
+ {
+ builder.Add(transition);
+ }
+
+ ParseUnconditionalBlock(builder);
+ ParseWhileClause(builder);
+ var topLevel = transition != null;
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseWhileClause(in SyntaxListBuilder builder)
+ {
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ var whitespace = SkipToNextImportantToken(builder);
+
+ if (At(CSharpKeyword.While))
+ {
+ Accept(whitespace);
+ Assert(CSharpKeyword.While);
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (TryParseCondition(builder) && TryAccept(SyntaxKind.Semicolon))
+ {
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ }
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(whitespace);
+ }
+ }
+
+ private void ParseUsingKeyword(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Assert(CSharpKeyword.Using);
+ var topLevel = transition != null;
+ var block = new Block(CurrentToken, CurrentStart);
+ var usingToken = EatCurrentToken();
+ var whitespaceOrComments = ReadWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ var atLeftParen = At(SyntaxKind.LeftParenthesis);
+ var atIdentifier = At(SyntaxKind.Identifier);
+ var atStatic = At(CSharpKeyword.Static);
+
+ // Put the read tokens back and let them be handled later.
+ PutCurrentBack();
+ PutBack(whitespaceOrComments);
+ PutBack(usingToken);
+ EnsureCurrent();
+
+ if (atLeftParen)
+ {
+ // using ( ==> Using Statement
+ ParseUsingStatement(builder, transition, block);
+ }
+ else if (atIdentifier || atStatic)
+ {
+ // using Identifier ==> Using Declaration
+ if (!topLevel)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_NamespaceImportAndTypeAliasCannotExistWithinCodeBlock(
+ new SourceSpan(block.Start, block.Name.Length)));
+ if (transition != null)
+ {
+ builder.Add(transition);
+ }
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ ParseStandardStatement(builder);
+ }
+ else
+ {
+ ParseUsingDeclaration(builder, transition);
+ return;
+ }
+ }
+ else
+ {
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ }
+
+ if (topLevel)
+ {
+ CompleteBlock();
+ }
+ }
+
+ private void ParseUsingStatement(in SyntaxListBuilder builder, CSharpTransitionSyntax transition, Block block)
+ {
+ Assert(CSharpKeyword.Using);
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ Assert(SyntaxKind.LeftParenthesis);
+ if (transition != null)
+ {
+ builder.Add(transition);
+ }
+
+ // Parse condition
+ if (TryParseCondition(builder))
+ {
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // Parse code block
+ ParseExpectedCodeBlock(builder, block);
+ }
+ }
+
+ private void ParseUsingDeclaration(in SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ // Using declarations should always be top level. The error case is handled in a different code path.
+ Debug.Assert(transition != null);
+ using (var pooledResult = Pool.Allocate())
+ {
+ var directiveBuilder = pooledResult.Builder;
+ Assert(CSharpKeyword.Using);
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ var start = CurrentStart;
+ if (At(SyntaxKind.Identifier))
+ {
+ // non-static using
+ TryParseNamespaceOrTypeName(directiveBuilder);
+ var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(SyntaxKind.Assign))
+ {
+ // Alias
+ Accept(whitespace);
+ Assert(SyntaxKind.Assign);
+ AcceptAndMoveNext();
+
+ AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+
+ // One more namespace or type name
+ TryParseNamespaceOrTypeName(directiveBuilder);
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(whitespace);
+ }
+ }
+ else if (At(CSharpKeyword.Static))
+ {
+ // static using
+ AcceptAndMoveNext();
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+ TryParseNamespaceOrTypeName(directiveBuilder);
+ }
+
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AnyExceptNewline;
+ SpanContext.ChunkGenerator = new AddImportChunkGenerator(new LocationTagged(
+ string.Concat(TokenBuilder.ToList().Nodes.Skip(1).Select(s => s.Content)),
+ start));
+
+ // Optional ";"
+ if (EnsureCurrent())
+ {
+ TryAccept(SyntaxKind.Semicolon);
+ }
+
+ CompleteBlock();
+ Debug.Assert(directiveBuilder.Count == 0, "We should not have built any blocks so far.");
+ var keywordTokens = OutputTokensAsStatementLiteral();
+ var directiveBody = SyntaxFactory.RazorDirectiveBody(keywordTokens, null);
+ builder.Add(SyntaxFactory.RazorDirective(transition, directiveBody));
+ }
+ }
+
+ private bool TryParseNamespaceOrTypeName(in SyntaxListBuilder builder)
+ {
+ if (TryAccept(SyntaxKind.LeftParenthesis))
+ {
+ while (!TryAccept(SyntaxKind.RightParenthesis) && !EndOfFile)
+ {
+ TryAccept(SyntaxKind.Whitespace);
+
+ if (!TryParseNamespaceOrTypeName(builder))
+ {
+ return false;
+ }
+
+ TryAccept(SyntaxKind.Whitespace);
+ TryAccept(SyntaxKind.Identifier);
+ TryAccept(SyntaxKind.Whitespace);
+ TryAccept(SyntaxKind.Comma);
+ }
+
+ if (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.QuestionMark))
+ {
+ // Only accept the whitespace if we are going to consume the next token.
+ AcceptAndMoveNext();
+ }
+
+ TryAccept(SyntaxKind.QuestionMark); // Nullable
+
+ return true;
+ }
+ else if (TryAccept(SyntaxKind.Identifier) || TryAccept(SyntaxKind.Keyword))
+ {
+ if (TryAccept(SyntaxKind.DoubleColon))
+ {
+ if (!TryAccept(SyntaxKind.Identifier))
+ {
+ TryAccept(SyntaxKind.Keyword);
+ }
+ }
+ if (At(SyntaxKind.LessThan))
+ {
+ ParseTypeArgumentList(builder);
+ }
+ if (TryAccept(SyntaxKind.Dot))
+ {
+ TryParseNamespaceOrTypeName(builder);
+ }
+
+ if (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.QuestionMark))
+ {
+ // Only accept the whitespace if we are going to consume the next token.
+ AcceptAndMoveNext();
+ }
+
+ TryAccept(SyntaxKind.QuestionMark); // Nullable
+
+ if (At(SyntaxKind.Whitespace) && NextIs(SyntaxKind.LeftBracket))
+ {
+ // Only accept the whitespace if we are going to consume the next token.
+ AcceptAndMoveNext();
+ }
+
+ while (At(SyntaxKind.LeftBracket))
+ {
+ Balance(builder, BalancingModes.None);
+ if (!TryAccept(SyntaxKind.RightBracket))
+ {
+ Accept(SyntaxFactory.MissingToken(SyntaxKind.RightBracket));
+ }
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private void ParseTypeArgumentList(in SyntaxListBuilder builder)
+ {
+ Assert(SyntaxKind.LessThan);
+ Balance(builder, BalancingModes.None);
+ if (!TryAccept(SyntaxKind.GreaterThan))
+ {
+ Accept(SyntaxFactory.MissingToken(SyntaxKind.GreaterThan));
+ }
+ }
+
+ private void ParseReservedDirective(SyntaxListBuilder builder, CSharpTransitionSyntax transition)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ReservedWord(
+ new SourceSpan(CurrentStart, CurrentToken.Content.Length), CurrentToken.Content));
+
+ AcceptAndMoveNext();
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
+ SpanContext.ChunkGenerator = SpanChunkGenerator.Null;
CompleteBlock();
- Output(SpanKindInternal.Code, AcceptedCharactersInternal.AnyExceptNewline);
+ var keyword = OutputAsMetaCode(Output());
+ var directiveBody = SyntaxFactory.RazorDirectiveBody(keyword, cSharpCode: null);
+ var directive = SyntaxFactory.RazorDirective(transition, directiveBody);
+ builder.Add(directive);
+ }
+
+ protected void CompleteBlock()
+ {
+ CompleteBlock(insertMarkerIfNecessary: true);
+ }
+
+ protected void CompleteBlock(bool insertMarkerIfNecessary)
+ {
+ CompleteBlock(insertMarkerIfNecessary, captureWhitespaceToEndOfLine: insertMarkerIfNecessary);
+ }
+
+ protected void CompleteBlock(bool insertMarkerIfNecessary, bool captureWhitespaceToEndOfLine)
+ {
+ if (insertMarkerIfNecessary && Context.LastAcceptedCharacters != AcceptedCharactersInternal.Any)
+ {
+ AcceptMarkerTokenIfNecessary();
+ }
+
+ EnsureCurrent();
+
+ // Read whitespace, but not newlines
+ // If we're not inserting a marker span, we don't need to capture whitespace
+ if (!Context.WhiteSpaceIsSignificantToAncestorBlock &&
+ captureWhitespaceToEndOfLine &&
+ !Context.DesignTimeMode &&
+ !IsNested)
+ {
+ var whitespace = ReadWhile(token => token.Kind == SyntaxKind.Whitespace);
+ if (At(SyntaxKind.NewLine))
+ {
+ Accept(whitespace);
+ AcceptAndMoveNext();
+ PutCurrentBack();
+ }
+ else
+ {
+ PutCurrentBack();
+ PutBack(whitespace);
+ }
+ }
+ else
+ {
+ PutCurrentBack();
+ }
+ }
+
+ private IEnumerable SkipToNextImportantToken(in SyntaxListBuilder builder)
+ {
+ while (!EndOfFile)
+ {
+ var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
+ if (At(SyntaxKind.RazorCommentTransition))
+ {
+ Accept(whitespace);
+ SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
+ AcceptMarkerTokenIfNecessary();
+ builder.Add(OutputTokensAsStatementLiteral());
+ var comment = ParseRazorComment();
+ builder.Add(comment);
+ }
+ else
+ {
+ return whitespace;
+ }
+ }
+ return Enumerable.Empty();
+ }
+
+ private void DefaultSpanContextConfig(SpanContextBuilder spanContext)
+ {
+ spanContext.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
+ spanContext.ChunkGenerator = new StatementChunkGenerator();
+ }
+
+ private void ExplicitExpressionSpanContextConfig(SpanContextBuilder spanContext)
+ {
+ spanContext.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
+ spanContext.ChunkGenerator = new ExpressionChunkGenerator();
+ }
+
+ private CSharpStatementLiteralSyntax OutputTokensAsStatementLiteral()
+ {
+ var tokens = Output();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+ return GetNodeWithSpanContext(SyntaxFactory.CSharpStatementLiteral(tokens));
+ }
+
+ private CSharpExpressionLiteralSyntax OutputTokensAsExpressionLiteral()
+ {
+ var tokens = Output();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+ return GetNodeWithSpanContext(SyntaxFactory.CSharpExpressionLiteral(tokens));
+ }
+
+ private CSharpEphemeralTextLiteralSyntax OutputTokensAsEphemeralLiteral()
+ {
+ var tokens = Output();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+ return GetNodeWithSpanContext(SyntaxFactory.CSharpEphemeralTextLiteral(tokens));
+ }
+
+ private UnclassifiedTextLiteralSyntax OutputTokensAsUnclassifiedLiteral()
+ {
+ var tokens = Output();
+ if (tokens.Count == 0)
+ {
+ return null;
+ }
+
+
+ return GetNodeWithSpanContext(SyntaxFactory.UnclassifiedTextLiteral(tokens));
+ }
+
+ private void OtherParserBlock(in SyntaxListBuilder builder)
+ {
+ // When transitioning to the HTML parser we no longer want to act as if we're in a nested C# state.
+ // For instance, if @hello.
is in a nested C# block we don't want the trailing '.' to be handled
+ // as C#; it should be handled as a period because it's wrapped in markup.
+ var wasNested = IsNested;
+ IsNested = false;
+
+ RazorSyntaxNode htmlBlock = null;
+ using (PushSpanContextConfig())
+ {
+ htmlBlock = HtmlParser.ParseBlock();
+ }
+
+ builder.Add(htmlBlock);
+ InitializeContext(SpanContext);
+
+ IsNested = wasNested;
+ NextToken();
+ }
+
+ private bool Balance(SyntaxListBuilder builder, BalancingModes mode)
+ {
+ var left = CurrentToken.Kind;
+ var right = Language.FlipBracket(left);
+ var start = CurrentStart;
+ AcceptAndMoveNext();
+ if (EndOfFile && ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure))
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedCloseBracketBeforeEOF(
+ new SourceSpan(start, contentLength: 1 /* { OR } */),
+ Language.GetSample(left),
+ Language.GetSample(right)));
+ }
+
+ return Balance(builder, mode, left, right, start);
+ }
+
+ private bool Balance(SyntaxListBuilder builder, BalancingModes mode, SyntaxKind left, SyntaxKind right, SourceLocation start)
+ {
+ var startPosition = CurrentStart.AbsoluteIndex;
+ var nesting = 1;
+ if (!EndOfFile)
+ {
+ var tokens = new List();
+ do
+ {
+ if (IsAtEmbeddedTransition(
+ (mode & BalancingModes.AllowCommentsAndTemplates) == BalancingModes.AllowCommentsAndTemplates,
+ (mode & BalancingModes.AllowEmbeddedTransitions) == BalancingModes.AllowEmbeddedTransitions))
+ {
+ Accept(tokens);
+ tokens.Clear();
+ ParseEmbeddedTransition(builder);
+
+ // Reset backtracking since we've already outputted some spans.
+ startPosition = CurrentStart.AbsoluteIndex;
+ }
+ if (At(left))
+ {
+ nesting++;
+ }
+ else if (At(right))
+ {
+ nesting--;
+ }
+ if (nesting > 0)
+ {
+ tokens.Add(CurrentToken);
+ }
+ }
+ while (nesting > 0 && NextToken());
+
+ if (nesting > 0)
+ {
+ if ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure)
+ {
+ Context.ErrorSink.OnError(
+ RazorDiagnosticFactory.CreateParsing_ExpectedCloseBracketBeforeEOF(
+ new SourceSpan(start, contentLength: 1 /* { OR } */),
+ Language.GetSample(left),
+ Language.GetSample(right)));
+ }
+ if ((mode & BalancingModes.BacktrackOnFailure) == BalancingModes.BacktrackOnFailure)
+ {
+ Context.Source.Position = startPosition;
+ NextToken();
+ }
+ else
+ {
+ Accept(tokens);
+ }
+ }
+ else
+ {
+ // Accept all the tokens we saw
+ Accept(tokens);
+ }
+ }
+ return nesting == 0;
+ }
+
+ private bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
+ {
+ // No embedded transitions in C#, so ignore that param
+ return allowTemplatesAndComments
+ && ((Language.IsTransition(CurrentToken)
+ && NextIs(SyntaxKind.LessThan, SyntaxKind.Colon, SyntaxKind.DoubleColon))
+ || Language.IsCommentStart(CurrentToken));
+ }
+
+ private void ParseEmbeddedTransition(in SyntaxListBuilder builder)
+ {
+ if (Language.IsTransition(CurrentToken))
+ {
+ PutCurrentBack();
+ ParseTemplate(builder);
+ }
+ else if (Language.IsCommentStart(CurrentToken))
+ {
+ // Output tokens before parsing the comment.
+ AcceptMarkerTokenIfNecessary();
+ if (SpanContext.ChunkGenerator is ExpressionChunkGenerator)
+ {
+ builder.Add(OutputTokensAsExpressionLiteral());
+ }
+ else
+ {
+ builder.Add(OutputTokensAsStatementLiteral());
+ }
+
+ var comment = ParseRazorComment();
+ builder.Add(comment);
+ }
+ }
+
+ [Conditional("DEBUG")]
+ internal void Assert(CSharpKeyword expectedKeyword)
+ {
+ var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
+ Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword &&
+ result.HasValue &&
+ result.Value == expectedKeyword);
+ }
+
+ protected internal bool At(CSharpKeyword keyword)
+ {
+ var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
+ return At(SyntaxKind.Keyword) &&
+ result.HasValue &&
+ result.Value == keyword;
+ }
+
+ protected static Func IsSpacingToken(bool includeNewLines, bool includeComments)
+ {
+ return token => token.Kind == SyntaxKind.Whitespace ||
+ (includeNewLines && token.Kind == SyntaxKind.NewLine) ||
+ (includeComments && token.Kind == SyntaxKind.CSharpComment);
}
protected class Block
@@ -2170,7 +2440,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Start = start;
}
- public Block(CSharpToken token, SourceLocation start)
+ public Block(SyntaxToken token, SourceLocation start)
: this(GetName(token), start)
{
}
@@ -2178,11 +2448,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public string Name { get; set; }
public SourceLocation Start { get; set; }
- private static string GetName(CSharpToken token)
+ private static string GetName(SyntaxToken token)
{
- if (token.Type == CSharpTokenType.Keyword)
+ var result = CSharpTokenizer.GetTokenKeyword(token);
+ if (result.HasValue && token.Kind == SyntaxKind.Keyword)
{
- return CSharpLanguageCharacteristics.GetKeyword(token.Keyword.Value);
+ return CSharpLanguageCharacteristics.GetKeyword(result.Value);
}
return token.Content;
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpLanguageCharacteristics.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpLanguageCharacteristics.cs
index 70d4bfeda1..3a017b54de 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpLanguageCharacteristics.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpLanguageCharacteristics.cs
@@ -3,64 +3,65 @@
using System.Collections.Generic;
using System.Diagnostics;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class CSharpLanguageCharacteristics : LanguageCharacteristics
+ internal class CSharpLanguageCharacteristics : LanguageCharacteristics
{
private static readonly CSharpLanguageCharacteristics _instance = new CSharpLanguageCharacteristics();
- private static Dictionary _tokenSamples = new Dictionary()
+ private static Dictionary _tokenSamples = new Dictionary()
{
- { CSharpTokenType.Arrow, "->" },
- { CSharpTokenType.Minus, "-" },
- { CSharpTokenType.Decrement, "--" },
- { CSharpTokenType.MinusAssign, "-=" },
- { CSharpTokenType.NotEqual, "!=" },
- { CSharpTokenType.Not, "!" },
- { CSharpTokenType.Modulo, "%" },
- { CSharpTokenType.ModuloAssign, "%=" },
- { CSharpTokenType.AndAssign, "&=" },
- { CSharpTokenType.And, "&" },
- { CSharpTokenType.DoubleAnd, "&&" },
- { CSharpTokenType.LeftParenthesis, "(" },
- { CSharpTokenType.RightParenthesis, ")" },
- { CSharpTokenType.Star, "*" },
- { CSharpTokenType.MultiplyAssign, "*=" },
- { CSharpTokenType.Comma, "," },
- { CSharpTokenType.Dot, "." },
- { CSharpTokenType.Slash, "/" },
- { CSharpTokenType.DivideAssign, "/=" },
- { CSharpTokenType.DoubleColon, "::" },
- { CSharpTokenType.Colon, ":" },
- { CSharpTokenType.Semicolon, ";" },
- { CSharpTokenType.QuestionMark, "?" },
- { CSharpTokenType.NullCoalesce, "??" },
- { CSharpTokenType.RightBracket, "]" },
- { CSharpTokenType.LeftBracket, "[" },
- { CSharpTokenType.XorAssign, "^=" },
- { CSharpTokenType.Xor, "^" },
- { CSharpTokenType.LeftBrace, "{" },
- { CSharpTokenType.OrAssign, "|=" },
- { CSharpTokenType.DoubleOr, "||" },
- { CSharpTokenType.Or, "|" },
- { CSharpTokenType.RightBrace, "}" },
- { CSharpTokenType.Tilde, "~" },
- { CSharpTokenType.Plus, "+" },
- { CSharpTokenType.PlusAssign, "+=" },
- { CSharpTokenType.Increment, "++" },
- { CSharpTokenType.LessThan, "<" },
- { CSharpTokenType.LessThanEqual, "<=" },
- { CSharpTokenType.LeftShift, "<<" },
- { CSharpTokenType.LeftShiftAssign, "<<=" },
- { CSharpTokenType.Assign, "=" },
- { CSharpTokenType.Equals, "==" },
- { CSharpTokenType.GreaterThan, ">" },
- { CSharpTokenType.GreaterThanEqual, ">=" },
- { CSharpTokenType.RightShift, ">>" },
- { CSharpTokenType.RightShiftAssign, ">>=" },
- { CSharpTokenType.Hash, "#" },
- { CSharpTokenType.Transition, "@" },
+ { SyntaxKind.Arrow, "->" },
+ { SyntaxKind.Minus, "-" },
+ { SyntaxKind.Decrement, "--" },
+ { SyntaxKind.MinusAssign, "-=" },
+ { SyntaxKind.NotEqual, "!=" },
+ { SyntaxKind.Not, "!" },
+ { SyntaxKind.Modulo, "%" },
+ { SyntaxKind.ModuloAssign, "%=" },
+ { SyntaxKind.AndAssign, "&=" },
+ { SyntaxKind.And, "&" },
+ { SyntaxKind.DoubleAnd, "&&" },
+ { SyntaxKind.LeftParenthesis, "(" },
+ { SyntaxKind.RightParenthesis, ")" },
+ { SyntaxKind.Star, "*" },
+ { SyntaxKind.MultiplyAssign, "*=" },
+ { SyntaxKind.Comma, "," },
+ { SyntaxKind.Dot, "." },
+ { SyntaxKind.Slash, "/" },
+ { SyntaxKind.DivideAssign, "/=" },
+ { SyntaxKind.DoubleColon, "::" },
+ { SyntaxKind.Colon, ":" },
+ { SyntaxKind.Semicolon, ";" },
+ { SyntaxKind.QuestionMark, "?" },
+ { SyntaxKind.NullCoalesce, "??" },
+ { SyntaxKind.RightBracket, "]" },
+ { SyntaxKind.LeftBracket, "[" },
+ { SyntaxKind.XorAssign, "^=" },
+ { SyntaxKind.Xor, "^" },
+ { SyntaxKind.LeftBrace, "{" },
+ { SyntaxKind.OrAssign, "|=" },
+ { SyntaxKind.DoubleOr, "||" },
+ { SyntaxKind.Or, "|" },
+ { SyntaxKind.RightBrace, "}" },
+ { SyntaxKind.Tilde, "~" },
+ { SyntaxKind.Plus, "+" },
+ { SyntaxKind.PlusAssign, "+=" },
+ { SyntaxKind.Increment, "++" },
+ { SyntaxKind.LessThan, "<" },
+ { SyntaxKind.LessThanEqual, "<=" },
+ { SyntaxKind.LeftShift, "<<" },
+ { SyntaxKind.LeftShiftAssign, "<<=" },
+ { SyntaxKind.Assign, "=" },
+ { SyntaxKind.Equals, "==" },
+ { SyntaxKind.GreaterThan, ">" },
+ { SyntaxKind.GreaterThanEqual, ">=" },
+ { SyntaxKind.RightShift, ">>" },
+ { SyntaxKind.RightShiftAssign, ">>=" },
+ { SyntaxKind.Hash, "#" },
+ { SyntaxKind.Transition, "@" },
};
protected CSharpLanguageCharacteristics()
@@ -74,35 +75,35 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return new CSharpTokenizer(source);
}
- protected override CSharpToken CreateToken(string content, CSharpTokenType type, IReadOnlyList errors)
+ protected override SyntaxToken CreateToken(string content, SyntaxKind kind, IReadOnlyList errors)
{
- return new CSharpToken(content, type, errors);
+ return SyntaxFactory.Token(kind, content, errors);
}
- public override string GetSample(CSharpTokenType type)
+ public override string GetSample(SyntaxKind kind)
{
string sample;
- if (!_tokenSamples.TryGetValue(type, out sample))
+ if (!_tokenSamples.TryGetValue(kind, out sample))
{
- switch (type)
+ switch (kind)
{
- case CSharpTokenType.Identifier:
+ case SyntaxKind.Identifier:
return Resources.CSharpToken_Identifier;
- case CSharpTokenType.Keyword:
+ case SyntaxKind.Keyword:
return Resources.CSharpToken_Keyword;
- case CSharpTokenType.IntegerLiteral:
+ case SyntaxKind.IntegerLiteral:
return Resources.CSharpToken_IntegerLiteral;
- case CSharpTokenType.NewLine:
+ case SyntaxKind.NewLine:
return Resources.CSharpToken_Newline;
- case CSharpTokenType.WhiteSpace:
+ case SyntaxKind.Whitespace:
return Resources.CSharpToken_Whitespace;
- case CSharpTokenType.Comment:
+ case SyntaxKind.CSharpComment:
return Resources.CSharpToken_Comment;
- case CSharpTokenType.RealLiteral:
+ case SyntaxKind.RealLiteral:
return Resources.CSharpToken_RealLiteral;
- case CSharpTokenType.CharacterLiteral:
+ case SyntaxKind.CharacterLiteral:
return Resources.CSharpToken_CharacterLiteral;
- case CSharpTokenType.StringLiteral:
+ case SyntaxKind.StringLiteral:
return Resources.CSharpToken_StringLiteral;
default:
return Resources.Token_Unknown;
@@ -111,59 +112,59 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return sample;
}
- public override CSharpToken CreateMarkerToken()
+ public override SyntaxToken CreateMarkerToken()
{
- return new CSharpToken(string.Empty, CSharpTokenType.Unknown);
+ return SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
}
- public override CSharpTokenType GetKnownTokenType(KnownTokenType type)
+ public override SyntaxKind GetKnownTokenType(KnownTokenType type)
{
switch (type)
{
case KnownTokenType.Identifier:
- return CSharpTokenType.Identifier;
+ return SyntaxKind.Identifier;
case KnownTokenType.Keyword:
- return CSharpTokenType.Keyword;
+ return SyntaxKind.Keyword;
case KnownTokenType.NewLine:
- return CSharpTokenType.NewLine;
- case KnownTokenType.WhiteSpace:
- return CSharpTokenType.WhiteSpace;
+ return SyntaxKind.NewLine;
+ case KnownTokenType.Whitespace:
+ return SyntaxKind.Whitespace;
case KnownTokenType.Transition:
- return CSharpTokenType.Transition;
+ return SyntaxKind.Transition;
case KnownTokenType.CommentStart:
- return CSharpTokenType.RazorCommentTransition;
+ return SyntaxKind.RazorCommentTransition;
case KnownTokenType.CommentStar:
- return CSharpTokenType.RazorCommentStar;
+ return SyntaxKind.RazorCommentStar;
case KnownTokenType.CommentBody:
- return CSharpTokenType.RazorComment;
+ return SyntaxKind.RazorCommentLiteral;
default:
- return CSharpTokenType.Unknown;
+ return SyntaxKind.Marker;
}
}
- public override CSharpTokenType FlipBracket(CSharpTokenType bracket)
+ public override SyntaxKind FlipBracket(SyntaxKind bracket)
{
switch (bracket)
{
- case CSharpTokenType.LeftBrace:
- return CSharpTokenType.RightBrace;
- case CSharpTokenType.LeftBracket:
- return CSharpTokenType.RightBracket;
- case CSharpTokenType.LeftParenthesis:
- return CSharpTokenType.RightParenthesis;
- case CSharpTokenType.LessThan:
- return CSharpTokenType.GreaterThan;
- case CSharpTokenType.RightBrace:
- return CSharpTokenType.LeftBrace;
- case CSharpTokenType.RightBracket:
- return CSharpTokenType.LeftBracket;
- case CSharpTokenType.RightParenthesis:
- return CSharpTokenType.LeftParenthesis;
- case CSharpTokenType.GreaterThan:
- return CSharpTokenType.LessThan;
+ case SyntaxKind.LeftBrace:
+ return SyntaxKind.RightBrace;
+ case SyntaxKind.LeftBracket:
+ return SyntaxKind.RightBracket;
+ case SyntaxKind.LeftParenthesis:
+ return SyntaxKind.RightParenthesis;
+ case SyntaxKind.LessThan:
+ return SyntaxKind.GreaterThan;
+ case SyntaxKind.RightBrace:
+ return SyntaxKind.LeftBrace;
+ case SyntaxKind.RightBracket:
+ return SyntaxKind.LeftBracket;
+ case SyntaxKind.RightParenthesis:
+ return SyntaxKind.LeftParenthesis;
+ case SyntaxKind.GreaterThan:
+ return SyntaxKind.LessThan;
default:
Debug.Fail("FlipBracket must be called with a bracket character");
- return CSharpTokenType.Unknown;
+ return SyntaxKind.Marker;
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpToken.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpToken.cs
deleted file mode 100644
index c928f0880e..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpToken.cs
+++ /dev/null
@@ -1,49 +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.Legacy
-{
- internal class CSharpToken : TokenBase
- {
- public CSharpToken(
- string content,
- CSharpTokenType type)
- : base(content, type, RazorDiagnostic.EmptyArray)
- {
- if (content == null)
- {
- throw new ArgumentNullException(nameof(content));
- }
- }
-
- public CSharpToken(
- string content,
- CSharpTokenType type,
- IReadOnlyList errors)
- : base(content, type, errors)
- {
- if (content == null)
- {
- throw new ArgumentNullException(nameof(content));
- }
- }
-
- public CSharpKeyword? Keyword { get; set; }
-
- public override bool Equals(object obj)
- {
- var other = obj as CSharpToken;
- return base.Equals(other) &&
- other.Keyword == Keyword;
- }
-
- public override int GetHashCode()
- {
- // Hash code should include only immutable properties.
- return base.GetHashCode();
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenType.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenType.cs
deleted file mode 100644
index 10f3478d6d..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenType.cs
+++ /dev/null
@@ -1,75 +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.Razor.Language.Legacy
-{
- internal enum CSharpTokenType
- {
- Unknown,
- Identifier,
- Keyword,
- IntegerLiteral,
- NewLine,
- WhiteSpace,
- Comment,
- RealLiteral,
- CharacterLiteral,
- StringLiteral,
-
- // Operators
- Arrow,
- Minus,
- Decrement,
- MinusAssign,
- NotEqual,
- Not,
- Modulo,
- ModuloAssign,
- AndAssign,
- And,
- DoubleAnd,
- LeftParenthesis,
- RightParenthesis,
- Star,
- MultiplyAssign,
- Comma,
- Dot,
- Slash,
- DivideAssign,
- DoubleColon,
- Colon,
- Semicolon,
- QuestionMark,
- NullCoalesce,
- RightBracket,
- LeftBracket,
- XorAssign,
- Xor,
- LeftBrace,
- OrAssign,
- DoubleOr,
- Or,
- RightBrace,
- Tilde,
- Plus,
- PlusAssign,
- Increment,
- LessThan,
- LessThanEqual,
- LeftShift,
- LeftShiftAssign,
- Assign,
- Equals,
- GreaterThan,
- GreaterThanEqual,
- RightShift,
- RightShiftAssign,
- Hash,
- Transition,
-
- // Razor specific
- RazorCommentTransition,
- RazorCommentStar,
- RazorComment
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs
index f819e98a3a..21cd0dc00b 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpTokenizer.cs
@@ -5,12 +5,13 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class CSharpTokenizer : Tokenizer
+ internal class CSharpTokenizer : Tokenizer
{
- private Dictionary> _operatorHandlers;
+ private Dictionary> _operatorHandlers;
private static readonly Dictionary _keywords = new Dictionary(StringComparer.Ordinal)
{
@@ -100,31 +101,31 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
base.CurrentState = StartState;
- _operatorHandlers = new Dictionary>()
+ _operatorHandlers = new Dictionary>()
{
{ '-', MinusOperator },
{ '<', LessThanOperator },
{ '>', GreaterThanOperator },
- { '&', CreateTwoCharOperatorHandler(CSharpTokenType.And, '=', CSharpTokenType.AndAssign, '&', CSharpTokenType.DoubleAnd) },
- { '|', CreateTwoCharOperatorHandler(CSharpTokenType.Or, '=', CSharpTokenType.OrAssign, '|', CSharpTokenType.DoubleOr) },
- { '+', CreateTwoCharOperatorHandler(CSharpTokenType.Plus, '=', CSharpTokenType.PlusAssign, '+', CSharpTokenType.Increment) },
- { '=', CreateTwoCharOperatorHandler(CSharpTokenType.Assign, '=', CSharpTokenType.Equals, '>', CSharpTokenType.GreaterThanEqual) },
- { '!', CreateTwoCharOperatorHandler(CSharpTokenType.Not, '=', CSharpTokenType.NotEqual) },
- { '%', CreateTwoCharOperatorHandler(CSharpTokenType.Modulo, '=', CSharpTokenType.ModuloAssign) },
- { '*', CreateTwoCharOperatorHandler(CSharpTokenType.Star, '=', CSharpTokenType.MultiplyAssign) },
- { ':', CreateTwoCharOperatorHandler(CSharpTokenType.Colon, ':', CSharpTokenType.DoubleColon) },
- { '?', CreateTwoCharOperatorHandler(CSharpTokenType.QuestionMark, '?', CSharpTokenType.NullCoalesce) },
- { '^', CreateTwoCharOperatorHandler(CSharpTokenType.Xor, '=', CSharpTokenType.XorAssign) },
- { '(', () => CSharpTokenType.LeftParenthesis },
- { ')', () => CSharpTokenType.RightParenthesis },
- { '{', () => CSharpTokenType.LeftBrace },
- { '}', () => CSharpTokenType.RightBrace },
- { '[', () => CSharpTokenType.LeftBracket },
- { ']', () => CSharpTokenType.RightBracket },
- { ',', () => CSharpTokenType.Comma },
- { ';', () => CSharpTokenType.Semicolon },
- { '~', () => CSharpTokenType.Tilde },
- { '#', () => CSharpTokenType.Hash }
+ { '&', CreateTwoCharOperatorHandler(SyntaxKind.And, '=', SyntaxKind.AndAssign, '&', SyntaxKind.DoubleAnd) },
+ { '|', CreateTwoCharOperatorHandler(SyntaxKind.Or, '=', SyntaxKind.OrAssign, '|', SyntaxKind.DoubleOr) },
+ { '+', CreateTwoCharOperatorHandler(SyntaxKind.Plus, '=', SyntaxKind.PlusAssign, '+', SyntaxKind.Increment) },
+ { '=', CreateTwoCharOperatorHandler(SyntaxKind.Assign, '=', SyntaxKind.Equals, '>', SyntaxKind.GreaterThanEqual) },
+ { '!', CreateTwoCharOperatorHandler(SyntaxKind.Not, '=', SyntaxKind.NotEqual) },
+ { '%', CreateTwoCharOperatorHandler(SyntaxKind.Modulo, '=', SyntaxKind.ModuloAssign) },
+ { '*', CreateTwoCharOperatorHandler(SyntaxKind.Star, '=', SyntaxKind.MultiplyAssign) },
+ { ':', CreateTwoCharOperatorHandler(SyntaxKind.Colon, ':', SyntaxKind.DoubleColon) },
+ { '?', CreateTwoCharOperatorHandler(SyntaxKind.QuestionMark, '?', SyntaxKind.NullCoalesce) },
+ { '^', CreateTwoCharOperatorHandler(SyntaxKind.Xor, '=', SyntaxKind.XorAssign) },
+ { '(', () => SyntaxKind.LeftParenthesis },
+ { ')', () => SyntaxKind.RightParenthesis },
+ { '{', () => SyntaxKind.LeftBrace },
+ { '}', () => SyntaxKind.RightBrace },
+ { '[', () => SyntaxKind.LeftBracket },
+ { ']', () => SyntaxKind.RightBracket },
+ { ',', () => SyntaxKind.Comma },
+ { ';', () => SyntaxKind.Semicolon },
+ { '~', () => SyntaxKind.Tilde },
+ { '#', () => SyntaxKind.Hash }
};
}
@@ -132,11 +133,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
private new CSharpTokenizerState? CurrentState => (CSharpTokenizerState?)base.CurrentState;
- public override CSharpTokenType RazorCommentType => CSharpTokenType.RazorComment;
+ public override SyntaxKind RazorCommentKind => SyntaxKind.RazorCommentLiteral;
- public override CSharpTokenType RazorCommentTransitionType => CSharpTokenType.RazorCommentTransition;
+ public override SyntaxKind RazorCommentTransitionKind => SyntaxKind.RazorCommentTransition;
- public override CSharpTokenType RazorCommentStarType => CSharpTokenType.RazorCommentStar;
+ public override SyntaxKind RazorCommentStarKind => SyntaxKind.RazorCommentStar;
protected override StateResult Dispatch()
{
@@ -169,7 +170,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
// Optimize memory allocation by returning constants for the most frequent cases
- protected override string GetTokenContent(CSharpTokenType type)
+ protected override string GetTokenContent(SyntaxKind type)
{
var tokenLength = Buffer.Length;
@@ -177,7 +178,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
switch (type)
{
- case CSharpTokenType.IntegerLiteral:
+ case SyntaxKind.IntegerLiteral:
switch (Buffer[0])
{
case '0':
@@ -202,13 +203,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return "9";
}
break;
- case CSharpTokenType.NewLine:
+ case SyntaxKind.NewLine:
if (Buffer[0] == '\n')
{
return "\n";
}
break;
- case CSharpTokenType.WhiteSpace:
+ case SyntaxKind.Whitespace:
if (Buffer[0] == ' ')
{
return " ";
@@ -218,57 +219,57 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return "\t";
}
break;
- case CSharpTokenType.Minus:
+ case SyntaxKind.Minus:
return "-";
- case CSharpTokenType.Not:
+ case SyntaxKind.Not:
return "!";
- case CSharpTokenType.Modulo:
+ case SyntaxKind.Modulo:
return "%";
- case CSharpTokenType.And:
+ case SyntaxKind.And:
return "&";
- case CSharpTokenType.LeftParenthesis:
+ case SyntaxKind.LeftParenthesis:
return "(";
- case CSharpTokenType.RightParenthesis:
+ case SyntaxKind.RightParenthesis:
return ")";
- case CSharpTokenType.Star:
+ case SyntaxKind.Star:
return "*";
- case CSharpTokenType.Comma:
+ case SyntaxKind.Comma:
return ",";
- case CSharpTokenType.Dot:
+ case SyntaxKind.Dot:
return ".";
- case CSharpTokenType.Slash:
+ case SyntaxKind.Slash:
return "/";
- case CSharpTokenType.Colon:
+ case SyntaxKind.Colon:
return ":";
- case CSharpTokenType.Semicolon:
+ case SyntaxKind.Semicolon:
return ";";
- case CSharpTokenType.QuestionMark:
+ case SyntaxKind.QuestionMark:
return "?";
- case CSharpTokenType.RightBracket:
+ case SyntaxKind.RightBracket:
return "]";
- case CSharpTokenType.LeftBracket:
+ case SyntaxKind.LeftBracket:
return "[";
- case CSharpTokenType.Xor:
+ case SyntaxKind.Xor:
return "^";
- case CSharpTokenType.LeftBrace:
+ case SyntaxKind.LeftBrace:
return "{";
- case CSharpTokenType.Or:
+ case SyntaxKind.Or:
return "|";
- case CSharpTokenType.RightBrace:
+ case SyntaxKind.RightBrace:
return "}";
- case CSharpTokenType.Tilde:
+ case SyntaxKind.Tilde:
return "~";
- case CSharpTokenType.Plus:
+ case SyntaxKind.Plus:
return "+";
- case CSharpTokenType.LessThan:
+ case SyntaxKind.LessThan:
return "<";
- case CSharpTokenType.Assign:
+ case SyntaxKind.Assign:
return "=";
- case CSharpTokenType.GreaterThan:
+ case SyntaxKind.GreaterThan:
return ">";
- case CSharpTokenType.Hash:
+ case SyntaxKind.Hash:
return "#";
- case CSharpTokenType.Transition:
+ case SyntaxKind.Transition:
return "@";
}
@@ -277,53 +278,53 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
switch (type)
{
- case CSharpTokenType.NewLine:
+ case SyntaxKind.NewLine:
return "\r\n";
- case CSharpTokenType.Arrow:
+ case SyntaxKind.Arrow:
return "->";
- case CSharpTokenType.Decrement:
+ case SyntaxKind.Decrement:
return "--";
- case CSharpTokenType.MinusAssign:
+ case SyntaxKind.MinusAssign:
return "-=";
- case CSharpTokenType.NotEqual:
+ case SyntaxKind.NotEqual:
return "!=";
- case CSharpTokenType.ModuloAssign:
+ case SyntaxKind.ModuloAssign:
return "%=";
- case CSharpTokenType.AndAssign:
+ case SyntaxKind.AndAssign:
return "&=";
- case CSharpTokenType.DoubleAnd:
+ case SyntaxKind.DoubleAnd:
return "&&";
- case CSharpTokenType.MultiplyAssign:
+ case SyntaxKind.MultiplyAssign:
return "*=";
- case CSharpTokenType.DivideAssign:
+ case SyntaxKind.DivideAssign:
return "/=";
- case CSharpTokenType.DoubleColon:
+ case SyntaxKind.DoubleColon:
return "::";
- case CSharpTokenType.NullCoalesce:
+ case SyntaxKind.NullCoalesce:
return "??";
- case CSharpTokenType.XorAssign:
+ case SyntaxKind.XorAssign:
return "^=";
- case CSharpTokenType.OrAssign:
+ case SyntaxKind.OrAssign:
return "|=";
- case CSharpTokenType.DoubleOr:
+ case SyntaxKind.DoubleOr:
return "||";
- case CSharpTokenType.PlusAssign:
+ case SyntaxKind.PlusAssign:
return "+=";
- case CSharpTokenType.Increment:
+ case SyntaxKind.Increment:
return "++";
- case CSharpTokenType.LessThanEqual:
+ case SyntaxKind.LessThanEqual:
return "<=";
- case CSharpTokenType.LeftShift:
+ case SyntaxKind.LeftShift:
return "<<";
- case CSharpTokenType.Equals:
+ case SyntaxKind.Equals:
return "==";
- case CSharpTokenType.GreaterThanEqual:
+ case SyntaxKind.GreaterThanEqual:
if (Buffer[0] == '=')
{
return "=>";
}
return ">=";
- case CSharpTokenType.RightShift:
+ case SyntaxKind.RightShift:
return ">>";
@@ -333,9 +334,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
switch (type)
{
- case CSharpTokenType.LeftShiftAssign:
+ case SyntaxKind.LeftShiftAssign:
return "<<=";
- case CSharpTokenType.RightShiftAssign:
+ case SyntaxKind.RightShiftAssign:
return ">>=";
}
}
@@ -343,9 +344,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return base.GetTokenContent(type);
}
- protected override CSharpToken CreateToken(string content, CSharpTokenType type, IReadOnlyList errors)
+ protected override SyntaxToken CreateToken(string content, SyntaxKind kind, IReadOnlyList errors)
{
- return new CSharpToken(content, type, errors);
+ return SyntaxFactory.Token(kind, content, errors);
}
private StateResult Data()
@@ -359,13 +360,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
TakeCurrent();
}
- return Stay(EndToken(CSharpTokenType.NewLine));
+ return Stay(EndToken(SyntaxKind.NewLine));
}
else if (ParserHelpers.IsWhitespace(CurrentCharacter))
{
// CSharp Spec §2.3.3
TakeUntil(c => !ParserHelpers.IsWhitespace(c));
- return Stay(EndToken(CSharpTokenType.WhiteSpace));
+ return Stay(EndToken(SyntaxKind.Whitespace));
}
else if (IsIdentifierStart(CurrentCharacter))
{
@@ -390,7 +391,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
return RealLiteral();
}
- return Stay(Single(CSharpTokenType.Dot));
+ return Stay(Single(SyntaxKind.Dot));
case '/':
TakeCurrent();
if (CurrentCharacter == '/')
@@ -406,11 +407,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
else if (CurrentCharacter == '=')
{
TakeCurrent();
- return Stay(EndToken(CSharpTokenType.DivideAssign));
+ return Stay(EndToken(SyntaxKind.DivideAssign));
}
else
{
- return Stay(EndToken(CSharpTokenType.Slash));
+ return Stay(EndToken(SyntaxKind.Slash));
}
default:
return Stay(EndToken(Operator()));
@@ -429,78 +430,78 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
return Transition(
CSharpTokenizerState.AfterRazorCommentTransition,
- EndToken(CSharpTokenType.RazorCommentTransition));
+ EndToken(SyntaxKind.RazorCommentTransition));
}
else if (CurrentCharacter == '@')
{
// Could be escaped comment transition
return Transition(
CSharpTokenizerState.EscapedRazorCommentTransition,
- EndToken(CSharpTokenType.Transition));
+ EndToken(SyntaxKind.Transition));
}
- return Stay(EndToken(CSharpTokenType.Transition));
+ return Stay(EndToken(SyntaxKind.Transition));
}
private StateResult EscapedRazorCommentTransition()
{
TakeCurrent();
- return Transition(CSharpTokenizerState.Data, EndToken(CSharpTokenType.Transition));
+ return Transition(CSharpTokenizerState.Data, EndToken(SyntaxKind.Transition));
}
- private CSharpTokenType Operator()
+ private SyntaxKind Operator()
{
var first = CurrentCharacter;
TakeCurrent();
- Func handler;
+ Func handler;
if (_operatorHandlers.TryGetValue(first, out handler))
{
return handler();
}
- return CSharpTokenType.Unknown;
+ return SyntaxKind.Marker;
}
- private CSharpTokenType LessThanOperator()
+ private SyntaxKind LessThanOperator()
{
if (CurrentCharacter == '=')
{
TakeCurrent();
- return CSharpTokenType.LessThanEqual;
+ return SyntaxKind.LessThanEqual;
}
- return CSharpTokenType.LessThan;
+ return SyntaxKind.LessThan;
}
- private CSharpTokenType GreaterThanOperator()
+ private SyntaxKind GreaterThanOperator()
{
if (CurrentCharacter == '=')
{
TakeCurrent();
- return CSharpTokenType.GreaterThanEqual;
+ return SyntaxKind.GreaterThanEqual;
}
- return CSharpTokenType.GreaterThan;
+ return SyntaxKind.GreaterThan;
}
- private CSharpTokenType MinusOperator()
+ private SyntaxKind MinusOperator()
{
if (CurrentCharacter == '>')
{
TakeCurrent();
- return CSharpTokenType.Arrow;
+ return SyntaxKind.Arrow;
}
else if (CurrentCharacter == '-')
{
TakeCurrent();
- return CSharpTokenType.Decrement;
+ return SyntaxKind.Decrement;
}
else if (CurrentCharacter == '=')
{
TakeCurrent();
- return CSharpTokenType.MinusAssign;
+ return SyntaxKind.MinusAssign;
}
- return CSharpTokenType.Minus;
+ return SyntaxKind.Minus;
}
- private Func CreateTwoCharOperatorHandler(CSharpTokenType typeIfOnlyFirst, char second, CSharpTokenType typeIfBoth)
+ private Func CreateTwoCharOperatorHandler(SyntaxKind typeIfOnlyFirst, char second, SyntaxKind typeIfBoth)
{
return () =>
{
@@ -513,7 +514,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
};
}
- private Func CreateTwoCharOperatorHandler(CSharpTokenType typeIfOnlyFirst, char option1, CSharpTokenType typeIfOption1, char option2, CSharpTokenType typeIfOption2)
+ private Func CreateTwoCharOperatorHandler(SyntaxKind typeIfOnlyFirst, char option1, SyntaxKind typeIfOption1, char option2, SyntaxKind typeIfOption2)
{
return () =>
{
@@ -550,14 +551,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
RazorDiagnosticFactory.CreateParsing_UnterminatedStringLiteral(
new SourceSpan(CurrentStart, contentLength: 1 /* end of file */)));
}
- return Transition(CSharpTokenizerState.Data, EndToken(CSharpTokenType.StringLiteral));
+ return Transition(CSharpTokenizerState.Data, EndToken(SyntaxKind.StringLiteral));
}
- private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', CSharpTokenType.CharacterLiteral);
+ private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', SyntaxKind.CharacterLiteral);
- private StateResult QuotedStringLiteral() => QuotedLiteral('\"', CSharpTokenType.StringLiteral);
+ private StateResult QuotedStringLiteral() => QuotedLiteral('\"', SyntaxKind.StringLiteral);
- private StateResult QuotedLiteral(char quote, CSharpTokenType literalType)
+ private StateResult QuotedLiteral(char quote, SyntaxKind literalType)
{
TakeUntil(c => c == '\\' || c == quote || ParserHelpers.IsNewLine(c));
if (CurrentCharacter == '\\')
@@ -594,7 +595,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
RazorDiagnosticFactory.CreateParsing_BlockCommentNotTerminated(
new SourceSpan(CurrentStart, contentLength: 1 /* end of file */)));
- return Transition(CSharpTokenizerState.Data, EndToken(CSharpTokenType.Comment));
+ return Transition(CSharpTokenizerState.Data, EndToken(SyntaxKind.CSharpComment));
}
if (CurrentCharacter == '*')
{
@@ -602,7 +603,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
if (CurrentCharacter == '/')
{
TakeCurrent();
- return Transition(CSharpTokenizerState.Data, EndToken(CSharpTokenType.Comment));
+ return Transition(CSharpTokenizerState.Data, EndToken(SyntaxKind.CSharpComment));
}
}
return Stay();
@@ -612,7 +613,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
private StateResult SingleLineComment()
{
TakeUntil(c => ParserHelpers.IsNewLine(c));
- return Stay(EndToken(CSharpTokenType.Comment));
+ return Stay(EndToken(SyntaxKind.CSharpComment));
}
// CSharp Spec §2.4.4
@@ -632,7 +633,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
TakeUntil(c => !IsHexDigit(c));
TakeIntegerSuffix();
- return Stay(EndToken(CSharpTokenType.IntegerLiteral));
+ return Stay(EndToken(SyntaxKind.IntegerLiteral));
}
private StateResult DecimalLiteral()
@@ -650,7 +651,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
else
{
TakeIntegerSuffix();
- return Stay(EndToken(CSharpTokenType.IntegerLiteral));
+ return Stay(EndToken(SyntaxKind.IntegerLiteral));
}
}
@@ -669,7 +670,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
TakeCurrent();
}
- return Stay(EndToken(CSharpTokenType.RealLiteral));
+ return Stay(EndToken(SyntaxKind.RealLiteral));
}
// CSharp Spec §2.4.4.3
@@ -708,21 +709,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
Debug.Assert(IsIdentifierStart(CurrentCharacter));
TakeCurrent();
TakeUntil(c => !IsIdentifierPart(c));
- CSharpToken token = null;
+ SyntaxToken token = null;
if (HaveContent)
{
CSharpKeyword keyword;
- var type = CSharpTokenType.Identifier;
+ var type = SyntaxKind.Identifier;
var tokenContent = Buffer.ToString();
if (_keywords.TryGetValue(tokenContent, out keyword))
{
- type = CSharpTokenType.Keyword;
+ type = SyntaxKind.Keyword;
}
-
- token = new CSharpToken(tokenContent, type)
- {
- Keyword = type == CSharpTokenType.Keyword ? (CSharpKeyword?)keyword : null,
- };
+
+ token = SyntaxFactory.Token(type, tokenContent);
Buffer.Clear();
CurrentErrors.Clear();
@@ -736,7 +734,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return Transition((int)state, result: null);
}
- private StateResult Transition(CSharpTokenizerState state, CSharpToken result)
+ private StateResult Transition(CSharpTokenizerState state, SyntaxToken result)
{
return Transition((int)state, result);
}
@@ -780,6 +778,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return (value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f');
}
+ internal static CSharpKeyword? GetTokenKeyword(SyntaxToken token)
+ {
+ if (token != null && _keywords.TryGetValue(token.Content, out var keyword))
+ {
+ return keyword;
+ }
+
+ return null;
+ }
+
private enum CSharpTokenizerState
{
Data,
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ChunkGeneratorContext.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ChunkGeneratorContext.cs
deleted file mode 100644
index b4a4a4782e..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ChunkGeneratorContext.cs
+++ /dev/null
@@ -1,25 +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.Razor.Language.Legacy
-{
- internal class ChunkGeneratorContext
- {
- public ChunkGeneratorContext(
- string className,
- string rootNamespace,
- string sourceFile,
- bool shouldGenerateLinePragmas)
- {
- SourceFile = shouldGenerateLinePragmas ? sourceFile : null;
- RootNamespace = rootNamespace;
- ClassName = className;
- }
-
- public string SourceFile { get; internal set; }
-
- public string RootNamespace { get; }
-
- public string ClassName { get; }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CodeBlockEditHandler.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CodeBlockEditHandler.cs
index d5ce71c43e..8ecbd85906 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CodeBlockEditHandler.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/CodeBlockEditHandler.cs
@@ -4,16 +4,17 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using Microsoft.AspNetCore.Razor.Language.Syntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class CodeBlockEditHandler : SpanEditHandler
{
- public CodeBlockEditHandler(Func> tokenizer) : base(tokenizer)
+ public CodeBlockEditHandler(Func> tokenizer) : base(tokenizer)
{
}
- protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
+ protected override PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
{
if (IsAcceptableDeletion(target, change))
{
@@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
// Internal for testing
- internal static bool IsAcceptableReplacement(Span target, SourceChange change)
+ internal static bool IsAcceptableReplacement(SyntaxNode target, SourceChange change)
{
if (!change.IsReplace)
{
@@ -55,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
// Internal for testing
- internal static bool IsAcceptableDeletion(Span target, SourceChange change)
+ internal static bool IsAcceptableDeletion(SyntaxNode target, SourceChange change)
{
if (!change.IsDelete)
{
@@ -71,11 +72,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
// Internal for testing
- internal static bool ModifiesInvalidContent(Span target, SourceChange change)
+ internal static bool ModifiesInvalidContent(SyntaxNode target, SourceChange change)
{
- var relativePosition = change.Span.AbsoluteIndex - target.Start.AbsoluteIndex;
+ var relativePosition = change.Span.AbsoluteIndex - target.Position;
- if (target.Content.IndexOfAny(new[] { '{', '}' }, relativePosition, change.Span.Length) >= 0)
+ if (target.GetContent().IndexOfAny(new[] { '{', '}' }, relativePosition, change.Span.Length) >= 0)
{
return true;
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ConditionalAttributeCollapser.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ConditionalAttributeCollapser.cs
deleted file mode 100644
index 4fa89603dc..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ConditionalAttributeCollapser.cs
+++ /dev/null
@@ -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.Diagnostics;
-using System.Text;
-
-namespace Microsoft.AspNetCore.Razor.Language.Legacy
-{
- internal class ConditionalAttributeCollapser : MarkupRewriter
- {
- protected override bool CanRewrite(Block block)
- {
- var generator = block.ChunkGenerator as AttributeBlockChunkGenerator;
- if (generator != null && block.Children.Count > 0)
- {
- // Perf: Avoid allocating an enumerator.
- for (var i = 0; i < block.Children.Count; i++)
- {
- if (!IsLiteralAttributeValue(block.Children[i]))
- {
- return false;
- }
- }
-
- return true;
- }
-
- return false;
- }
-
- protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
- {
- // Collect the content of this node
- var builder = new StringBuilder();
- for (var i = 0; i < block.Children.Count; i++)
- {
- var childSpan = (Span)block.Children[i];
- builder.Append(childSpan.Content);
- }
-
- // Create a new span containing this content
- var span = new SpanBuilder(block.Children[0].Start);
-
- span.EditHandler = SpanEditHandler.CreateDefault(HtmlLanguageCharacteristics.Instance.TokenizeString);
- Debug.Assert(block.Children.Count > 0);
- var start = ((Span)block.Children[0]).Start;
- FillSpan(span, start, builder.ToString());
- return span.Build();
- }
-
- private bool IsLiteralAttributeValue(SyntaxTreeNode node)
- {
- if (node.IsBlock)
- {
- return false;
- }
-
- var span = node as Span;
- Debug.Assert(span != null);
-
- return span != null &&
- (span.ChunkGenerator is LiteralAttributeChunkGenerator ||
- span.ChunkGenerator is MarkupChunkGenerator ||
- span.ChunkGenerator == SpanChunkGenerator.Null);
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveCSharpTokenizer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveCSharpTokenizer.cs
index a9befaa302..59224e709f 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveCSharpTokenizer.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveCSharpTokenizer.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
@@ -17,11 +18,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
protected override StateResult Dispatch()
{
var result = base.Dispatch();
- if (result.Result != null && !_visitedFirstTokenStart && IsValidTokenType(result.Result.Type))
+ if (result.Result != null && !_visitedFirstTokenStart && IsValidTokenType(result.Result.Kind))
{
_visitedFirstTokenStart = true;
}
- else if (result.Result != null && _visitedFirstTokenStart && result.Result.Type == CSharpTokenType.NewLine)
+ else if (result.Result != null && _visitedFirstTokenStart && result.Result.Kind == SyntaxKind.NewLine)
{
_visitedFirstTokenLineEnd = true;
}
@@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return result;
}
- public override CSharpToken NextToken()
+ public override SyntaxToken NextToken()
{
// Post-Condition: Buffer should be empty at the start of Next()
Debug.Assert(Buffer.Length == 0);
@@ -48,15 +49,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return token;
}
- private bool IsValidTokenType(CSharpTokenType type)
+ private bool IsValidTokenType(SyntaxKind kind)
{
- return type != CSharpTokenType.WhiteSpace &&
- type != CSharpTokenType.NewLine &&
- type != CSharpTokenType.Comment &&
- type != CSharpTokenType.RazorComment &&
- type != CSharpTokenType.RazorCommentStar &&
- type != CSharpTokenType.RazorCommentTransition &&
- type != CSharpTokenType.Transition;
+ return kind != SyntaxKind.Whitespace &&
+ kind != SyntaxKind.NewLine &&
+ kind != SyntaxKind.CSharpComment &&
+ kind != SyntaxKind.RazorCommentLiteral &&
+ kind != SyntaxKind.RazorCommentStar &&
+ kind != SyntaxKind.RazorCommentTransition &&
+ kind != SyntaxKind.Transition;
}
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveChunkGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveChunkGenerator.cs
deleted file mode 100644
index 19a6bad463..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveChunkGenerator.cs
+++ /dev/null
@@ -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.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.Extensions.Internal;
-
-namespace Microsoft.AspNetCore.Razor.Language.Legacy
-{
- internal class DirectiveChunkGenerator : ParentChunkGenerator
- {
- private static readonly Type Type = typeof(DirectiveChunkGenerator);
- private List _diagnostics;
-
- public DirectiveChunkGenerator(DirectiveDescriptor descriptor)
- {
- Descriptor = descriptor;
- }
-
- public DirectiveDescriptor Descriptor { get; }
-
- public List Diagnostics
- {
- get
- {
- if (_diagnostics == null)
- {
- _diagnostics = new List();
- }
-
- return _diagnostics;
- }
- }
-
- public override void Accept(ParserVisitor visitor, Block block)
- {
- visitor.VisitDirectiveBlock(this, block);
- }
-
- public override bool Equals(object obj)
- {
- var other = obj as DirectiveChunkGenerator;
- return base.Equals(other) &&
- Enumerable.SequenceEqual(Diagnostics, other.Diagnostics) &&
- DirectiveDescriptorComparer.Default.Equals(Descriptor, other.Descriptor);
- }
-
- public override int GetHashCode()
- {
- var combiner = HashCodeCombiner.Start();
- combiner.Add(base.GetHashCode());
- combiner.Add(Type);
-
- return combiner.CombinedHash;
- }
-
- public override string ToString()
- {
- // This is used primarily at test time to show an identifiable representation of the chunk generator.
-
- var builder = new StringBuilder("Directive:{");
- builder.Append(Descriptor.Directive);
- builder.Append(";");
- builder.Append(Descriptor.Kind);
- builder.Append(";");
- builder.Append(Descriptor.Usage);
- builder.Append("}");
-
- if (Diagnostics.Count > 0)
- {
- builder.Append(" [");
- var ids = string.Join(", ", Diagnostics.Select(diagnostic => $"{diagnostic.Id}{diagnostic.Span}"));
- builder.Append(ids);
- builder.Append("]");
- }
-
- return builder.ToString();
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveHtmlTokenizer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveHtmlTokenizer.cs
index 034977c758..4921c8aa1e 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveHtmlTokenizer.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveHtmlTokenizer.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
@@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
protected override StateResult Dispatch()
{
var result = base.Dispatch();
- if (result.Result != null && IsValidTokenType(result.Result.Type))
+ if (result.Result != null && IsValidTokenType(result.Result.Kind))
{
_visitedFirstTokenStart = true;
}
@@ -24,7 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return result;
}
- public override HtmlToken NextToken()
+ public override SyntaxToken NextToken()
{
// Post-Condition: Buffer should be empty at the start of Next()
Debug.Assert(Buffer.Length == 0);
@@ -43,14 +44,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return token;
}
- private bool IsValidTokenType(HtmlTokenType type)
+ private bool IsValidTokenType(SyntaxKind kind)
{
- return type != HtmlTokenType.WhiteSpace &&
- type != HtmlTokenType.NewLine &&
- type != HtmlTokenType.RazorComment &&
- type != HtmlTokenType.RazorCommentStar &&
- type != HtmlTokenType.RazorCommentTransition &&
- type != HtmlTokenType.Transition;
+ return kind != SyntaxKind.Whitespace &&
+ kind != SyntaxKind.NewLine &&
+ kind != SyntaxKind.RazorCommentLiteral &&
+ kind != SyntaxKind.RazorCommentStar &&
+ kind != SyntaxKind.RazorCommentTransition &&
+ kind != SyntaxKind.Transition;
}
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveTokenChunkGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveTokenChunkGenerator.cs
index 69327ba6e2..ea1095bd5b 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveTokenChunkGenerator.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DirectiveTokenChunkGenerator.cs
@@ -18,11 +18,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public DirectiveTokenDescriptor Descriptor { get; }
- public override void Accept(ParserVisitor visitor, Span span)
- {
- visitor.VisitDirectiveToken(this, span);
- }
-
public override bool Equals(object obj)
{
var other = obj as DirectiveTokenChunkGenerator;
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DynamicAttributeBlockChunkGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DynamicAttributeBlockChunkGenerator.cs
deleted file mode 100644
index 5b8c31efae..0000000000
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/DynamicAttributeBlockChunkGenerator.cs
+++ /dev/null
@@ -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 System;
-using System.Globalization;
-
-namespace Microsoft.AspNetCore.Razor.Language.Legacy
-{
- internal class DynamicAttributeBlockChunkGenerator : ParentChunkGenerator
- {
- public DynamicAttributeBlockChunkGenerator(LocationTagged prefix, int offset, int line, int col)
- : this(prefix, new SourceLocation(offset, line, col))
- {
- }
-
- public DynamicAttributeBlockChunkGenerator(LocationTagged prefix, SourceLocation valueStart)
- {
- Prefix = prefix;
- ValueStart = valueStart;
- }
-
- public LocationTagged Prefix { get; }
-
- public SourceLocation ValueStart { get; }
-
- public override void Accept(ParserVisitor visitor, Block block)
- {
- visitor.VisitDynamicAttributeBlock(this, block);
- }
-
- public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
- {
- //var chunk = context.ChunkTreeBuilder.StartParentChunk(target);
- //chunk.Start = ValueStart;
- //chunk.Prefix = Prefix;
- }
-
- public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
- {
- //context.ChunkTreeBuilder.EndParentChunk();
- }
-
- public override string ToString()
- {
- return string.Format(CultureInfo.CurrentCulture, "DynAttr:{0:F}", Prefix);
- }
-
- public override bool Equals(object obj)
- {
- var other = obj as DynamicAttributeBlockChunkGenerator;
- return other != null &&
- Equals(other.Prefix, Prefix);
- }
-
- public override int GetHashCode()
- {
- return Prefix == null ? 0 : Prefix.GetHashCode();
- }
- }
-}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/EditResult.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/EditResult.cs
index 6aff8dea24..13dfb20a71 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/EditResult.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/EditResult.cs
@@ -1,17 +1,19 @@
// 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.Syntax;
+
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class EditResult
{
- public EditResult(PartialParseResultInternal result, SpanBuilder editedSpan)
+ public EditResult(PartialParseResultInternal result, SyntaxNode editedNode)
{
Result = result;
- EditedSpan = editedSpan;
+ EditedNode = editedNode;
}
public PartialParseResultInternal Result { get; set; }
- public SpanBuilder EditedSpan { get; set; }
+ public SyntaxNode EditedNode { get; set; }
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ExpressionChunkGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ExpressionChunkGenerator.cs
index fd2ef3ab91..24eb488c1e 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ExpressionChunkGenerator.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/ExpressionChunkGenerator.cs
@@ -5,35 +5,10 @@ using System;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator
+ internal class ExpressionChunkGenerator : ISpanChunkGenerator
{
private static readonly int TypeHashCode = typeof(ExpressionChunkGenerator).GetHashCode();
- public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
- {
- //context.ChunkTreeBuilder.StartParentChunk(target);
- }
-
- public void GenerateChunk(Span target, ChunkGeneratorContext context)
- {
- //context.ChunkTreeBuilder.AddExpressionChunk(target.Content, target);
- }
-
- public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
- {
- //context.ChunkTreeBuilder.EndParentChunk();
- }
-
- public void Accept(ParserVisitor visitor, Span span)
- {
- visitor.VisitExpressionSpan(this, span);
- }
-
- public void Accept(ParserVisitor visitor, Block block)
- {
- visitor.VisitExpressionBlock(this, block);
- }
-
public override string ToString()
{
return "Expr";
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs
index 096ae84dc0..5efd2b80a5 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlLanguageCharacteristics.cs
@@ -3,10 +3,11 @@
using System.Collections.Generic;
using System.Diagnostics;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class HtmlLanguageCharacteristics : LanguageCharacteristics
+ internal class HtmlLanguageCharacteristics : LanguageCharacteristics
{
private static readonly HtmlLanguageCharacteristics _instance = new HtmlLanguageCharacteristics();
@@ -19,47 +20,47 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
get { return _instance; }
}
- public override string GetSample(HtmlTokenType type)
+ public override string GetSample(SyntaxKind type)
{
switch (type)
{
- case HtmlTokenType.Text:
+ case SyntaxKind.Text:
return Resources.HtmlToken_Text;
- case HtmlTokenType.WhiteSpace:
+ case SyntaxKind.Whitespace:
return Resources.HtmlToken_WhiteSpace;
- case HtmlTokenType.NewLine:
+ case SyntaxKind.NewLine:
return Resources.HtmlToken_NewLine;
- case HtmlTokenType.OpenAngle:
+ case SyntaxKind.OpenAngle:
return "<";
- case HtmlTokenType.Bang:
+ case SyntaxKind.Bang:
return "!";
- case HtmlTokenType.ForwardSlash:
+ case SyntaxKind.ForwardSlash:
return "/";
- case HtmlTokenType.QuestionMark:
+ case SyntaxKind.QuestionMark:
return "?";
- case HtmlTokenType.DoubleHyphen:
+ case SyntaxKind.DoubleHyphen:
return "--";
- case HtmlTokenType.LeftBracket:
+ case SyntaxKind.LeftBracket:
return "[";
- case HtmlTokenType.CloseAngle:
+ case SyntaxKind.CloseAngle:
return ">";
- case HtmlTokenType.RightBracket:
+ case SyntaxKind.RightBracket:
return "]";
- case HtmlTokenType.Equals:
+ case SyntaxKind.Equals:
return "=";
- case HtmlTokenType.DoubleQuote:
+ case SyntaxKind.DoubleQuote:
return "\"";
- case HtmlTokenType.SingleQuote:
+ case SyntaxKind.SingleQuote:
return "'";
- case HtmlTokenType.Transition:
+ case SyntaxKind.Transition:
return "@";
- case HtmlTokenType.Colon:
+ case SyntaxKind.Colon:
return ":";
- case HtmlTokenType.RazorComment:
+ case SyntaxKind.RazorCommentLiteral:
return Resources.HtmlToken_RazorComment;
- case HtmlTokenType.RazorCommentStar:
+ case SyntaxKind.RazorCommentStar:
return "*";
- case HtmlTokenType.RazorCommentTransition:
+ case SyntaxKind.RazorCommentTransition:
return "@";
default:
return Resources.Token_Unknown;
@@ -71,57 +72,57 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return new HtmlTokenizer(source);
}
- public override HtmlTokenType FlipBracket(HtmlTokenType bracket)
+ public override SyntaxKind FlipBracket(SyntaxKind bracket)
{
switch (bracket)
{
- case HtmlTokenType.LeftBracket:
- return HtmlTokenType.RightBracket;
- case HtmlTokenType.OpenAngle:
- return HtmlTokenType.CloseAngle;
- case HtmlTokenType.RightBracket:
- return HtmlTokenType.LeftBracket;
- case HtmlTokenType.CloseAngle:
- return HtmlTokenType.OpenAngle;
+ case SyntaxKind.LeftBracket:
+ return SyntaxKind.RightBracket;
+ case SyntaxKind.OpenAngle:
+ return SyntaxKind.CloseAngle;
+ case SyntaxKind.RightBracket:
+ return SyntaxKind.LeftBracket;
+ case SyntaxKind.CloseAngle:
+ return SyntaxKind.OpenAngle;
default:
Debug.Fail("FlipBracket must be called with a bracket character");
- return HtmlTokenType.Unknown;
+ return SyntaxKind.Marker;
}
}
- public override HtmlToken CreateMarkerToken()
+ public override SyntaxToken CreateMarkerToken()
{
- return new HtmlToken(string.Empty, HtmlTokenType.Unknown);
+ return SyntaxFactory.Token(SyntaxKind.Marker, string.Empty);
}
- public override HtmlTokenType GetKnownTokenType(KnownTokenType type)
+ public override SyntaxKind GetKnownTokenType(KnownTokenType type)
{
switch (type)
{
case KnownTokenType.CommentStart:
- return HtmlTokenType.RazorCommentTransition;
+ return SyntaxKind.RazorCommentTransition;
case KnownTokenType.CommentStar:
- return HtmlTokenType.RazorCommentStar;
+ return SyntaxKind.RazorCommentStar;
case KnownTokenType.CommentBody:
- return HtmlTokenType.RazorComment;
+ return SyntaxKind.RazorCommentLiteral;
case KnownTokenType.Identifier:
- return HtmlTokenType.Text;
+ return SyntaxKind.Text;
case KnownTokenType.Keyword:
- return HtmlTokenType.Text;
+ return SyntaxKind.Text;
case KnownTokenType.NewLine:
- return HtmlTokenType.NewLine;
+ return SyntaxKind.NewLine;
case KnownTokenType.Transition:
- return HtmlTokenType.Transition;
- case KnownTokenType.WhiteSpace:
- return HtmlTokenType.WhiteSpace;
+ return SyntaxKind.Transition;
+ case KnownTokenType.Whitespace:
+ return SyntaxKind.Whitespace;
default:
- return HtmlTokenType.Unknown;
+ return SyntaxKind.Marker;
}
}
- protected override HtmlToken CreateToken(string content, HtmlTokenType type, IReadOnlyList errors)
+ protected override SyntaxToken CreateToken(string content, SyntaxKind kind, IReadOnlyList errors)
{
- return new HtmlToken(content, type, errors);
+ return SyntaxFactory.Token(kind, content, errors);
}
}
}
diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs
index 2408c552dd..f06c8db1bc 100644
--- a/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs
+++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Language/Legacy/HtmlMarkupParser.cs
@@ -5,22 +5,27 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
- internal class HtmlMarkupParser : TokenizerBackedParser
+ internal class HtmlMarkupParser : TokenizerBackedParser
{
private const string ScriptTagName = "script";
- private static readonly HtmlToken[] nonAllowedHtmlCommentEnding = new[] { HtmlToken.Hyphen, new HtmlToken("!", HtmlTokenType.Bang), new HtmlToken("<", HtmlTokenType.OpenAngle) };
- private static readonly HtmlToken[] singleHyphenArray = new[] { HtmlToken.Hyphen };
+ private static readonly SyntaxToken[] nonAllowedHtmlCommentEnding = new[]
+ {
+ SyntaxFactory.Token(SyntaxKind.Text, "-"),
+ SyntaxFactory.Token(SyntaxKind.Bang, "!"),
+ SyntaxFactory.Token(SyntaxKind.OpenAngle, "<"),
+ };
private static readonly char[] ValidAfterTypeAttributeNameCharacters = { ' ', '\t', '\r', '\n', '\f', '=' };
private SourceLocation _lastTagStart = SourceLocation.Zero;
- private HtmlToken _bufferedOpenAngle;
+ private SyntaxToken _bufferedOpenAngle;
//From http://dev.w3.org/html5/spec/Overview.html#elements-0
- private ISet _voidElements = new HashSet(StringComparer.OrdinalIgnoreCase)
+ private readonly ISet _voidElements = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"area",
"base",
@@ -45,7 +50,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
}
- public ParserBase CodeParser { get; set; }
+ public CSharpCodeParser CodeParser { get; set; }
public ISet VoidElements
{
@@ -59,44 +64,75 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
get { return CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; }
}
- protected override bool TokenTypeEquals(HtmlTokenType x, HtmlTokenType y) => x == y;
-
- public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
+ // Special tags include "
- if (CurrentToken.Type != HtmlTokenType.DoubleHyphen)
+ if (CurrentToken.Kind != SyntaxKind.DoubleHyphen)
{
return false;
}
// Check condition 2.1
- if (NextIs(HtmlTokenType.CloseAngle) || NextIs(next => IsHyphen(next) && NextIs(HtmlTokenType.CloseAngle)))
+ if (NextIs(SyntaxKind.CloseAngle) || NextIs(next => IsHyphen(next) && NextIs(SyntaxKind.CloseAngle)))
{
return false;
}
@@ -610,15 +811,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var isValidComment = false;
LookaheadUntil((token, prevTokens) =>
{
- if (token.Type == HtmlTokenType.DoubleHyphen)
+ if (token.Kind == SyntaxKind.DoubleHyphen)
{
- if (NextIs(HtmlTokenType.CloseAngle))
+ if (NextIs(SyntaxKind.CloseAngle))
{
// Check condition 2.3: We're at the end of a comment. Check to make sure the text ending is allowed.
isValidComment = !IsCommentContentEndingInvalid(prevTokens);
return true;
}
- else if (NextIs(ns => IsHyphen(ns) && NextIs(HtmlTokenType.CloseAngle)))
+ else if (NextIs(ns => IsHyphen(ns) && NextIs(SyntaxKind.CloseAngle)))
{
// Check condition 2.3: we're at the end of a comment, which has an extra dash.
// Need to treat the dash as part of the content and check the ending.
@@ -627,17 +828,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
isValidComment = true;
return true;
}
- else if (NextIs(ns => ns.Type == HtmlTokenType.Bang && NextIs(HtmlTokenType.CloseAngle)))
+ else if (NextIs(ns => ns.Kind == SyntaxKind.Bang && NextIs(SyntaxKind.CloseAngle)))
{
// This is condition 2.2.3
isValidComment = false;
return true;
}
}
- else if (token.Type == HtmlTokenType.OpenAngle)
+ else if (token.Kind == SyntaxKind.OpenAngle)
{
// Checking condition 2.2.1
- if (NextIs(ns => ns.Type == HtmlTokenType.Bang && NextIs(HtmlTokenType.DoubleHyphen)))
+ if (NextIs(ns => ns.Kind == SyntaxKind.Bang && NextIs(SyntaxKind.DoubleHyphen)))
{
isValidComment = false;
return true;
@@ -653,13 +854,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
///
/// Verifies, that the sequence doesn't end with the "<!-" HtmlTokens. Note, the first token is an opening bracket token
///
- internal static bool IsCommentContentEndingInvalid(IEnumerable sequence)
+ internal static bool IsCommentContentEndingInvalid(IEnumerable sequence)
{
var reversedSequence = sequence.Reverse();
var index = 0;
foreach (var item in reversedSequence)
{
- if (!item.Equals(nonAllowedHtmlCommentEnding[index++]))
+ if (!item.IsEquivalentTo(nonAllowedHtmlCommentEnding[index++]))
{
return false;
}
@@ -673,15 +874,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return false;
}
- private bool CData()
+ private bool TryParseCData(in SyntaxListBuilder builder)
{
- if (CurrentToken.Type == HtmlTokenType.Text && string.Equals(CurrentToken.Content, "cdata", StringComparison.OrdinalIgnoreCase))
+ if (CurrentToken.Kind == SyntaxKind.Text && string.Equals(CurrentToken.Content, "cdata", StringComparison.OrdinalIgnoreCase))
{
if (AcceptAndMoveNext())
{
- if (CurrentToken.Type == HtmlTokenType.LeftBracket)
+ if (CurrentToken.Kind == SyntaxKind.LeftBracket)
{
- return AcceptUntilAll(HtmlTokenType.RightBracket, HtmlTokenType.RightBracket, HtmlTokenType.CloseAngle);
+ return AcceptTokenUntilAll(builder, SyntaxKind.RightBracket, SyntaxKind.RightBracket, SyntaxKind.CloseAngle);
}
}
}
@@ -689,701 +890,65 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return false;
}
- private bool EndTag(SourceLocation tagStart,
- Stack> tags,
- IDisposable tagBlockWrapper)
+ private bool TryParseXmlPI(in SyntaxListBuilder builder)
{
- // Accept "/" and move next
- Assert(HtmlTokenType.ForwardSlash);
- var forwardSlash = CurrentToken;
- if (!NextToken())
- {
- Accept(_bufferedOpenAngle);
- Accept(forwardSlash);
- return false;
- }
- else
- {
- var tagName = string.Empty;
- HtmlToken bangToken = null;
-
- if (At(HtmlTokenType.Bang))
- {
- bangToken = CurrentToken;
-
- var nextToken = Lookahead(count: 1);
-
- if (nextToken != null && nextToken.Type == HtmlTokenType.Text)
- {
- tagName = "!" + nextToken.Content;
- }
- }
- else if (At(HtmlTokenType.Text))
- {
- tagName = CurrentToken.Content;
- }
-
- var matched = RemoveTag(tags, tagName, tagStart);
-
- if (tags.Count == 0 &&
- // Note tagName may contain a '!' escape character. This ensures !text> doesn't match here.
- // !text> tags are treated like any other escaped HTML end tag.
- string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
- matched)
- {
- return EndTextTag(forwardSlash, tagBlockWrapper);
- }
- Accept(_bufferedOpenAngle);
- Accept(forwardSlash);
-
- OptionalBangEscape();
-
- AcceptUntil(HtmlTokenType.CloseAngle);
-
- // Accept the ">"
- return Optional(HtmlTokenType.CloseAngle);
- }
- }
-
- private void RecoverTextTag()
- {
- // We don't want to skip-to and parse because there shouldn't be anything in the body of text tags.
- AcceptUntil(HtmlTokenType.CloseAngle, HtmlTokenType.NewLine);
-
- // Include the close angle in the text tag block if it's there, otherwise just move on
- Optional(HtmlTokenType.CloseAngle);
- }
-
- private bool EndTextTag(HtmlToken solidus, IDisposable tagBlockWrapper)
- {
- Accept(_bufferedOpenAngle);
- Accept(solidus);
-
- var textLocation = CurrentStart;
- Assert(HtmlTokenType.Text);
+ // Accept "?"
+ Assert(SyntaxKind.QuestionMark);
AcceptAndMoveNext();
-
- var seenCloseAngle = Optional(HtmlTokenType.CloseAngle);
-
- if (!seenCloseAngle)
- {
- Context.ErrorSink.OnError(
- RazorDiagnosticFactory.CreateParsing_TextTagCannotContainAttributes(
- new SourceSpan(textLocation, contentLength: 4 /* text */)));
-
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
- RecoverTextTag();
- }
- else
- {
- Span.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
- }
-
- Span.ChunkGenerator = SpanChunkGenerator.Null;
-
- CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKindInternal.Transition);
-
- return seenCloseAngle;
+ return AcceptTokenUntilAll(builder, SyntaxKind.QuestionMark, SyntaxKind.CloseAngle);
}
- // Special tags include
+
+