From e2d476cc6f3cf62dca5523b9a85a049b50aa91f8 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 2 Aug 2019 09:35:57 -0700 Subject: [PATCH] Associate C# version with each Razor extension version. - Roslyn now default to 8.0 C# so it's no longer specified in a users project file. Because of this restriction we can no longer see the projects `LangVersion` when generating Razor files. Meaning, we can't conditionally generate C# 8.0 aware code because our Razor default is not C# 8.0. Therefore, when we detect that the C# lang version hasn't been provided we don't set a C# version and instead rely on what the default configuration for the Razor project is. In practice this looks like: - MVC1.X = C# 7.3 - MVC2.X = C# 7.3 - MVC Latest = C# 8.0 - I thought about adding a feature flags variant for `RazorCodeGenerationOptions` but decided not to since C# features are all configurable options that are based on more than just the `RazorLangVersion`. - Added integration tests to ensure that command line builds with implicit `LangVersion`s result in proper C# nullability handling. aspnet/AspNetCoredotnet/aspnetcore-tooling#12594 \n\nCommit migrated from https://github.com/dotnet/aspnetcore-tooling/commit/f039aa935462163dead64ca2d6f9c6d27f4e290b --- .../src/RazorExtensions.cs | 4 +++ .../src/RazorExtensions.cs | 4 +++ .../src/RazorExtensions.cs | 4 +++ .../src/GenerateCommand.cs | 31 ++++++++--------- .../RazorProjectEngineBuilderExtensions.cs | 2 +- .../IntegrationTests/BuildIntegrationTest.cs | 33 ++++++++++++++++++- 6 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/RazorExtensions.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/RazorExtensions.cs index f8cf7f5cb4..6fe73b57ae 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/RazorExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/RazorExtensions.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Extensions; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X @@ -38,6 +39,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X builder.Features.Add(new MvcViewDocumentClassifierPass()); builder.Features.Add(new MvcImportProjectFeature()); + + // The default C# language version for what this Razor configuration supports. + builder.SetCSharpLanguageVersion(LanguageVersion.CSharp7_3); } public static void RegisterViewComponentTagHelpers(RazorProjectEngineBuilder builder) diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/RazorExtensions.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/RazorExtensions.cs index 0e52eb00d1..afb0e896e0 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/RazorExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/RazorExtensions.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Extensions; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X @@ -44,6 +45,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X builder.Features.Add(new InstrumentationPass()); builder.Features.Add(new MvcImportProjectFeature()); + + // The default C# language version for what this Razor configuration supports. + builder.SetCSharpLanguageVersion(LanguageVersion.CSharp7_3); } } } diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs index 867586db5c..dfaebba007 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Extensions; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions @@ -39,6 +40,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions builder.Features.Add(new MvcViewDocumentClassifierPass()); builder.Features.Add(new MvcImportProjectFeature()); + + // The default C# language version for what this Razor configuration supports. + builder.SetCSharpLanguageVersion(LanguageVersion.CSharp8); } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs index ba38782ceb..7ebc74cb73 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs @@ -176,20 +176,6 @@ namespace Microsoft.AspNetCore.Razor.Tools }); var success = true; - var csharpLanguageVersion = LanguageVersion.Default; - if (CSharpLanguageVersion.HasValue()) - { - var rawLanguageVersion = CSharpLanguageVersion.Value(); - if (!LanguageVersionFacts.TryParse(CSharpLanguageVersion.Value(), out var parsedLanguageVersion)) - { - success = false; - Error.WriteLine($"Unknown C# language version {rawLanguageVersion}."); - } - else - { - csharpLanguageVersion = parsedLanguageVersion; - } - } var engine = RazorProjectEngine.Create(configuration, compositeFileSystem, b => { @@ -206,7 +192,22 @@ namespace Microsoft.AspNetCore.Razor.Tools b.SetRootNamespace(RootNamespace.Value()); } - b.SetCSharpLanguageVersion(csharpLanguageVersion); + if (CSharpLanguageVersion.HasValue()) + { + // Only set the C# language version if one was specified, otherwise it defaults to whatever + // value was set in the corresponding RazorConfiguration's extensions. + + var rawLanguageVersion = CSharpLanguageVersion.Value(); + if (LanguageVersionFacts.TryParse(rawLanguageVersion, out var csharpLanguageVersion)) + { + b.SetCSharpLanguageVersion(csharpLanguageVersion); + } + else + { + success = false; + Error.WriteLine($"Unknown C# language version {rawLanguageVersion}."); + } + } }); var results = GenerateCode(engine, sourceItems); diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.cs index 6e57ab1016..e492643723 100644 --- a/src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.cs +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.cs @@ -60,7 +60,7 @@ namespace Microsoft.CodeAnalysis.Razor throw new ArgumentNullException(nameof(options)); } - if (options.Configuration.LanguageVersion.Major < 3) + if (options.Configuration != null && options.Configuration.LanguageVersion.Major < 3) { // Prior to 3.0 there were no C# version specific controlled features. Suppress nullability enforcement. options.SuppressNullabilityEnforcement = true; diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/BuildIntegrationTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/BuildIntegrationTest.cs index 839f7da92b..9c9bce24d5 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/BuildIntegrationTest.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/BuildIntegrationTest.cs @@ -641,7 +641,38 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests [Fact] [InitializeTestProject("SimpleMvc")] - public async Task Build_CSharp8_NullableEnforcement_WarningsDuringBuild_BuildServer() + public async Task Build_ImplicitCSharp8_NullableEnforcement_WarningsDuringBuild_NoBuildServer() + { + var result = await DotnetMSBuild( + "Build", + "/p:Nullable=enable", + suppressBuildServer: true); + var indexFilePath = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs"); + + Assert.BuildPassed(result, allowWarnings: true); + Assert.BuildWarning(result, "CS8618"); + Assert.FileContainsLine(result, indexFilePath, "#nullable restore"); + Assert.FileContainsLine(result, indexFilePath, "#nullable disable"); + } + + [Fact] + [InitializeTestProject("SimpleMvc")] + public async Task Build_ExplicitCSharp73_NullableEnforcement_Disabled_NoNullableFeature_NoBuildServer() + { + var result = await DotnetMSBuild( + "Build", + "/p:LangVersion=7.3", + suppressBuildServer: true); + var indexFilePath = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.cshtml.g.cs"); + + Assert.BuildPassed(result, allowWarnings: false); + Assert.FileDoesNotContainLine(result, indexFilePath, "#nullable restore"); + Assert.FileDoesNotContainLine(result, indexFilePath, "#nullable disable"); + } + + [Fact] + [InitializeTestProject("SimpleMvc")] + public async Task Build_ExplicitCSharp8_NullableEnforcement_WarningsDuringBuild_BuildServer() { var result = await DotnetMSBuild( "Build",