From f33e1fca532a881351c39199bdcedf4c0d17cec1 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 19 Mar 2019 11:24:56 -0700 Subject: [PATCH] Enforce nullability for user code. - Expanded the `ProjectWorkspaceStateGenerator` to extract the C# language version when building the `ProjectWorkspaceState`. This approach enables all platforms to get nullability support without any changes (as long as they support `ProjectWorkspaceState`, which they do). Also, Roslyn suggested that we avoid dealing with LangVersion directly because there are several factors that impact its "effective" value on a project when run in tooling. - Updated the `LinePragma` code generation to include `#nullable restore` and `#nullable disable` lines to allow for project restored nullability state for user code. - Added a new `RazorProjectEngineBuilderExtensions` class that adds Roslyn specific project engine modifications. In this case it allows us to set the C# language version for a project engine and configure underlying features accordingly. - Added a `SuppressNullabilityEnforcement` flag that only turns on if C# < 8 is specified. - Updated LiveShare, VS4Mac and RazorGenerate to understand CSharpLanguageVersion. - Added a single test output to show the change. dotnet/aspnetcore-tooling#5092 \n\nCommit migrated from https://github.com/dotnet/aspnetcore-tooling/commit/1df8128b877ac9ffec5c7547e84927b0ecbe8c16 --- .../src/InjectTargetExtension.cs | 2 +- .../src/InjectTargetExtension.cs | 2 +- .../src/InjectTargetExtension.cs | 2 +- .../CodeGeneration/CodeWriterExtensions.cs | 22 ++++- .../CodeGeneration/DesignTimeNodeWriter.cs | 10 +-- .../src/CodeGeneration/RuntimeNodeWriter.cs | 10 +-- .../ComponentDesignTimeNodeWriter.cs | 10 +-- .../Components/ComponentRuntimeNodeWriter.cs | 2 +- .../src/DefaultRazorCodeGenerationOptions.cs | 6 +- ...efaultRazorCodeGenerationOptionsBuilder.cs | 5 +- .../DefaultTagHelperTargetExtension.cs | 4 +- .../DesignTimeDirectiveTargetExtension.cs | 10 +-- .../src/RazorCodeGenerationOptions.cs | 11 ++- .../src/RazorCodeGenerationOptionsBuilder.cs | 5 ++ .../src/GenerateCommand.cs | 24 +++++- .../RazorProjectEngineBuilderExtensions.cs | 85 +++++++++++++++++++ .../src/RazorGenerate.cs | 26 ++++-- ...osoft.NET.Sdk.Razor.CodeGeneration.targets | 1 + .../Microsoft.NET.Sdk.Razor.Component.targets | 1 + 19 files changed, 195 insertions(+), 43 deletions(-) create mode 100644 src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.cs diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/InjectTargetExtension.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/InjectTargetExtension.cs index 23effb4081..d64023ade7 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/InjectTargetExtension.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/src/InjectTargetExtension.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X if (node.Source.HasValue) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { context.CodeWriter .WriteLine(RazorInjectAttribute) diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/InjectTargetExtension.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/InjectTargetExtension.cs index 1660b84d6d..d2723ba99d 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/InjectTargetExtension.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X/src/InjectTargetExtension.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X if (node.Source.HasValue) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { context.CodeWriter .WriteLine(RazorInjectAttribute) diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/InjectTargetExtension.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/InjectTargetExtension.cs index 9569cca41f..d416d3aa0a 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/InjectTargetExtension.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/InjectTargetExtension.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions if (node.Source.HasValue) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { context.CodeWriter .WriteLine(RazorInjectAttribute) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/CodeWriterExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/CodeWriterExtensions.cs index f15acdeaf6..dedecfda82 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/CodeWriterExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/CodeWriterExtensions.cs @@ -440,7 +440,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration return new CSharpCodeWritingScope(writer); } - public static IDisposable BuildLinePragma(this CodeWriter writer, SourceSpan? span) + public static IDisposable BuildLinePragma(this CodeWriter writer, SourceSpan? span, CodeRenderingContext context) { if (string.IsNullOrEmpty(span?.FilePath)) { @@ -448,7 +448,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration return NullDisposable.Default; } - return new LinePragmaWriter(writer, span.Value); + return new LinePragmaWriter(writer, span.Value, context.Options); } private static void WriteVerbatimStringLiteral(CodeWriter writer, string literal) @@ -596,9 +596,13 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration private class LinePragmaWriter : IDisposable { private readonly CodeWriter _writer; + private readonly RazorCodeGenerationOptions _codeGenerationOptions; private readonly int _startIndent; - public LinePragmaWriter(CodeWriter writer, SourceSpan span) + public LinePragmaWriter( + CodeWriter writer, + SourceSpan span, + RazorCodeGenerationOptions codeGenerationOptions) { if (writer == null) { @@ -606,8 +610,15 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration } _writer = writer; + _codeGenerationOptions = codeGenerationOptions; _startIndent = _writer.CurrentIndent; _writer.CurrentIndent = 0; + + if (!_codeGenerationOptions.SuppressNullabilityEnforcement) + { + _writer.WriteLine("#nullable restore"); + } + WriteLineNumberDirective(writer, span); } @@ -630,6 +641,11 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration .WriteLine("#line default") .WriteLine("#line hidden"); + if (!_codeGenerationOptions.SuppressNullabilityEnforcement) + { + _writer.WriteLine("#nullable disable"); + } + _writer.CurrentIndent = _startIndent; } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/DesignTimeNodeWriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/DesignTimeNodeWriter.cs index d937c6f626..9bf56e2eec 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/DesignTimeNodeWriter.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/DesignTimeNodeWriter.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { if (node.Source.HasValue) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { context.AddSourceMappingFor(node); context.CodeWriter.WriteUsing(node.Content); @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration if (node.Source != null) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { var offset = DesignTimeDirectivePass.DesignTimeVariable.Length + " = ".Length; context.CodeWriter.WritePadding(offset, node.Source, context); @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration IDisposable linePragmaScope = null; if (node.Source != null) { - linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value); + linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value, context); context.CodeWriter.WritePadding(0, node.Source.Value, context); } @@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration var firstChild = node.Children[0]; if (firstChild.Source != null) { - using (context.CodeWriter.BuildLinePragma(firstChild.Source.Value)) + using (context.CodeWriter.BuildLinePragma(firstChild.Source.Value, context)) { var offset = DesignTimeDirectivePass.DesignTimeVariable.Length + " = ".Length; context.CodeWriter.WritePadding(offset, firstChild.Source, context); @@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { if (!isWhitespaceStatement) { - linePragmaScope = context.CodeWriter.BuildLinePragma(token.Source.Value); + linePragmaScope = context.CodeWriter.BuildLinePragma(token.Source.Value, context); } context.CodeWriter.WritePadding(0, token.Source.Value, context); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/RuntimeNodeWriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/RuntimeNodeWriter.cs index f913a53fc4..9d4561e84d 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/RuntimeNodeWriter.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/RuntimeNodeWriter.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { if (node.Source.HasValue) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { context.CodeWriter.WriteUsing(node.Content); } @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration IDisposable linePragmaScope = null; if (node.Source != null) { - linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value); + linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value, context); context.CodeWriter.WritePadding(WriteCSharpExpressionMethod.Length + 1, node.Source, context); } @@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration IDisposable linePragmaScope = null; if (node.Source != null) { - linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value); + linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value, context); context.CodeWriter.WritePadding(0, node.Source.Value, context); } @@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { var prefixLocation = node.Source.Value.AbsoluteIndex; context.CodeWriter @@ -266,7 +266,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { if (!isWhitespaceStatement) { - linePragmaScope = context.CodeWriter.BuildLinePragma(token.Source.Value); + linePragmaScope = context.CodeWriter.BuildLinePragma(token.Source.Value, context); } context.CodeWriter.WritePadding(0, token.Source.Value, context); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs index 9a3fd27c1e..359baefc66 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components if (node.Source.HasValue) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { context.AddSourceMappingFor(node); context.CodeWriter.WriteUsing(node.Content); @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components if (node.Source != null) { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) + using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { var offset = DesignTimeVariable.Length + " = ".Length; context.CodeWriter.WritePadding(offset, node.Source, context); @@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components { if (!isWhitespaceStatement) { - linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value); + linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value, context); } context.CodeWriter.WritePadding(0, node.Source.Value, context); @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components var firstChild = node.Children[0]; if (firstChild.Source != null) { - using (context.CodeWriter.BuildLinePragma(firstChild.Source.Value)) + using (context.CodeWriter.BuildLinePragma(firstChild.Source.Value, context)) { var offset = DesignTimeVariable.Length + " = ".Length; context.CodeWriter.WritePadding(offset, firstChild.Source, context); @@ -835,7 +835,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components return; } - using (context.CodeWriter.BuildLinePragma(token.Source)) + using (context.CodeWriter.BuildLinePragma(token.Source, context)) { context.CodeWriter.WritePadding(0, token.Source.Value, context); context.AddSourceMappingFor(token); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs index 96aa4ceb79..33b29255f4 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components IDisposable linePragmaScope = null; if (node.Source != null) { - linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value); + linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value, context); context.CodeWriter.WritePadding(0, node.Source.Value, context); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs index d97eb15af8..bb0179fc0b 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs @@ -12,7 +12,8 @@ namespace Microsoft.AspNetCore.Razor.Language string rootNamespace, bool suppressChecksum, bool supressMetadataAttributes, - bool suppressPrimaryMethodBody) + bool suppressPrimaryMethodBody, + bool suppressNullabilityEnforcement) { IndentWithTabs = indentWithTabs; IndentSize = indentSize; @@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Language SuppressChecksum = suppressChecksum; SuppressMetadataAttributes = supressMetadataAttributes; SuppressPrimaryMethodBody = suppressPrimaryMethodBody; + SuppressNullabilityEnforcement = suppressNullabilityEnforcement; } public override bool DesignTime { get; } @@ -32,5 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Language public override string RootNamespace { get; } public override bool SuppressChecksum { get; } + + public override bool SuppressNullabilityEnforcement { get; } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs index dbf795587f..12f2f119c4 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs @@ -37,6 +37,8 @@ namespace Microsoft.AspNetCore.Razor.Language public override bool SuppressChecksum { get; set; } + public override bool SuppressNullabilityEnforcement { get; set; } + public override RazorCodeGenerationOptions Build() { return new DefaultRazorCodeGenerationOptions( @@ -46,7 +48,8 @@ namespace Microsoft.AspNetCore.Razor.Language RootNamespace, SuppressChecksum, SuppressMetadataAttributes, - SuppressPrimaryMethodBody); + SuppressPrimaryMethodBody, + SuppressNullabilityEnforcement); } public override void SetDesignTime(bool designTime) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DefaultTagHelperTargetExtension.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DefaultTagHelperTargetExtension.cs index 884ef07df5..7273868d62 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DefaultTagHelperTargetExtension.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DefaultTagHelperTargetExtension.cs @@ -373,7 +373,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as IntermediateNode; var valueStart = firstMappedChild?.Source; - using (context.CodeWriter.BuildLinePragma(node.Source)) + using (context.CodeWriter.BuildLinePragma(node.Source, context)) { var accessor = GetPropertyAccessor(node); var assignmentPrefixLength = accessor.Length + " = ".Length; @@ -422,7 +422,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } else { - using (context.CodeWriter.BuildLinePragma(node.Source)) + using (context.CodeWriter.BuildLinePragma(node.Source, context)) { context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node)); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DesignTimeDirectiveTargetExtension.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DesignTimeDirectiveTargetExtension.cs index 61300f7efa..3823ab17e7 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DesignTimeDirectiveTargetExtension.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/DesignTimeDirectiveTargetExtension.cs @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } // {node.Content} __typeHelper = default({node.Content}); - using (context.CodeWriter.BuildLinePragma(node.Source)) + using (context.CodeWriter.BuildLinePragma(node.Source, context)) { context.AddSourceMappingFor(node); context.CodeWriter @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } // global::System.Object {node.content} = null; - using (context.CodeWriter.BuildLinePragma(node.Source)) + using (context.CodeWriter.BuildLinePragma(node.Source, context)) { context.CodeWriter .Write("global::") @@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } // global::System.Object __typeHelper = nameof({node.Content}); - using (context.CodeWriter.BuildLinePragma(node.Source)) + using (context.CodeWriter.BuildLinePragma(node.Source, context)) { context.CodeWriter .Write("global::") @@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions case DirectiveTokenKind.String: // global::System.Object __typeHelper = "{node.Content}"; - using (context.CodeWriter.BuildLinePragma(node.Source)) + using (context.CodeWriter.BuildLinePragma(node.Source, context)) { context.CodeWriter .Write("global::") @@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions // for consistency so when a C# completion session starts, filling user code doesn't result in // a previously non-existent line pragma from being added and destroying the context in which // the completion session was started. - using (context.CodeWriter.BuildLinePragma(node.Source)) + using (context.CodeWriter.BuildLinePragma(node.Source, context)) { context.AddSourceMappingFor(node); context.CodeWriter.Write(" "); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs index 4a3f479396..7eeb15388e 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs @@ -16,7 +16,8 @@ namespace Microsoft.AspNetCore.Razor.Language suppressChecksum: false, rootNamespace: null, supressMetadataAttributes: false, - suppressPrimaryMethodBody: false); + suppressPrimaryMethodBody: false, + suppressNullabilityEnforcement: false); } public static RazorCodeGenerationOptions CreateDesignTimeDefault() @@ -28,7 +29,8 @@ namespace Microsoft.AspNetCore.Razor.Language rootNamespace: null, suppressChecksum: false, supressMetadataAttributes: true, - suppressPrimaryMethodBody: false); + suppressPrimaryMethodBody: false, + suppressNullabilityEnforcement: false); } public static RazorCodeGenerationOptions Create(Action configure) @@ -107,5 +109,10 @@ namespace Microsoft.AspNetCore.Razor.Language /// Gets or sets a value that determines if an empty body is generated for the primary method. /// public virtual bool SuppressPrimaryMethodBody { get; protected set; } + + /// + /// Gets or sets a value that determines if nullability type enforcement is restored to project settings for user code. + /// + public virtual bool SuppressNullabilityEnforcement { get; } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs index e2813630c5..1493832ca9 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs @@ -54,6 +54,11 @@ namespace Microsoft.AspNetCore.Razor.Language /// public virtual bool SuppressPrimaryMethodBody { get; set; } + /// + /// Gets or sets a value that determines if nullability type enforcement is restored to project settings for user code. + /// + public virtual bool SuppressNullabilityEnforcement { get; set; } + public abstract RazorCodeGenerationOptions Build(); public virtual void SetDesignTime(bool designTime) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs index f2e1b76b7a..3501096bde 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; using Microsoft.Extensions.CommandLineUtils; using Microsoft.VisualStudio.LanguageServices.Razor.Serialization; @@ -30,6 +31,7 @@ namespace Microsoft.AspNetCore.Razor.Tools ExtensionNames = Option("-n", "extension name", CommandOptionType.MultipleValue); ExtensionFilePaths = Option("-e", "extension file path", CommandOptionType.MultipleValue); RootNamespace = Option("--root-namespace", "root namespace for generated code", CommandOptionType.SingleValue); + CSharpLanguageVersion = Option("--csharp-language-version", "csharp language version generated code", CommandOptionType.SingleValue); GenerateDeclaration = Option("--generate-declaration", "Generate declaration", CommandOptionType.NoValue); } @@ -55,6 +57,8 @@ namespace Microsoft.AspNetCore.Razor.Tools public CommandOption RootNamespace { get; } + public CommandOption CSharpLanguageVersion { get; } + public CommandOption GenerateDeclaration { get; } protected override Task ExecuteCoreAsync() @@ -170,6 +174,22 @@ namespace Microsoft.AspNetCore.Razor.Tools RazorProjectFileSystem.Create(projectDirectory), }); + 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 => { b.Features.Add(new StaticTagHelperFeature() { TagHelpers = tagHelpers, }); @@ -184,12 +204,12 @@ namespace Microsoft.AspNetCore.Razor.Tools { b.SetRootNamespace(RootNamespace.Value()); } + + b.SetCSharpLanguageVersion(csharpLanguageVersion); }); var results = GenerateCode(engine, sourceItems); - var success = true; - foreach (var result in results) { var errorCount = result.CSharpDocument.Diagnostics.Count; diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.cs new file mode 100644 index 0000000000..30def1be7c --- /dev/null +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/src/RazorProjectEngineBuilderExtensions.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.Linq; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.CodeAnalysis.Razor +{ + /// + /// Roslyn specific extensions. + /// + public static class RazorProjectEngineBuilderExtensions + { + /// + /// Sets the C# language version to respect when generating code. + /// + /// The . + /// The C# . + /// The . + public static RazorProjectEngineBuilder SetCSharpLanguageVersion(this RazorProjectEngineBuilder builder, LanguageVersion csharpLanguageVersion) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (builder.Configuration.LanguageVersion.Major < 3) + { + // Prior to 3.0 there were no C# version specific controlled features so there's no value in setting a CSharp language version, noop. + return builder; + } + + var existingFeature = builder.Features.OfType().FirstOrDefault(); + if (existingFeature != null) + { + builder.Features.Remove(existingFeature); + } + + builder.Features.Add(new ConfigureParserForCSharpVersionFeature(csharpLanguageVersion)); + + return builder; + } + + private class ConfigureParserForCSharpVersionFeature : IConfigureRazorCodeGenerationOptionsFeature + { + public ConfigureParserForCSharpVersionFeature(LanguageVersion csharpLanguageVersion) + { + CSharpLanguageVersion = csharpLanguageVersion; + } + + public LanguageVersion CSharpLanguageVersion { get; } + + public int Order { get; set; } + + public RazorEngine Engine { get; set; } + + public void Configure(RazorCodeGenerationOptionsBuilder options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (CSharpLanguageVersion < LanguageVersion.CSharp8) + { + options.SuppressNullabilityEnforcement = true; + } + else + { + // Given that nullability enforcement can be a compile error we only turn it on for C# >= 8.0. There are + // cases in tooling when the project isn't fully configured yet at which point the CSharpLanguageVersion + // may be Default (value 0). In those cases that C# version is equivalently "unspecified" and is up to the consumer + // to act in a safe manner to not cause unneeded errors for older compilers. Therefore if the version isn't + // >= 8.0 (or Latest) then nullability enforcement is suppressed. + // + // Once the project finishes configuration the C# language version will be updated to reflect the effective + // language version for the project. + options.SuppressNullabilityEnforcement = false; + } + } + } + } +} diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs index 6e8364eea5..bee0df6816 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs @@ -26,6 +26,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks public string RootNamespace { get; set; } + public string CSharpLanguageVersion { get; set; } + [Required] public string Version { get; set; } @@ -138,16 +140,24 @@ namespace Microsoft.AspNetCore.Razor.Tasks builder.AppendLine(Configuration[0].GetMetadata(Identity)); // Added in 3.0 - if (parsedVersion.Major >= 3 && !string.IsNullOrEmpty(RootNamespace)) + if (parsedVersion.Major >= 3) { - builder.AppendLine("--root-namespace"); - builder.AppendLine(RootNamespace); - } + if (!string.IsNullOrEmpty(RootNamespace)) + { + builder.AppendLine("--root-namespace"); + builder.AppendLine(RootNamespace); + } - // Added in 3.0 - if (parsedVersion.Major >= 3 && GenerateDeclaration) - { - builder.AppendLine("--generate-declaration"); + if (!string.IsNullOrEmpty(CSharpLanguageVersion)) + { + builder.AppendLine("--csharp-language-version"); + builder.AppendLine(CSharpLanguageVersion); + } + + if (GenerateDeclaration) + { + builder.AppendLine("--generate-declaration"); + } } for (var i = 0; i < Extensions.Length; i++) diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets index 89b921e422..52037f43c4 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets @@ -156,6 +156,7 @@ Copyright (c) .NET Foundation. All rights reserved. PipeName="$(_RazorBuildServerPipeName)" Version="$(RazorLangVersion)" RootNamespace="$(RootNamespace)" + CSharpLanguageVersion="$(LangVersion)" Configuration="@(ResolvedRazorConfiguration)" Extensions="@(ResolvedRazorExtension)" Sources="@(RazorGenerateWithTargetPath)" diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets index 4d5f644760..9ac1a9cd2c 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets @@ -107,6 +107,7 @@ Copyright (c) .NET Foundation. All rights reserved. PipeName="$(_RazorBuildServerPipeName)" Version="$(RazorLangVersion)" RootNamespace="$(RootNamespace)" + CSharpLanguageVersion="$(LangVersion)" Configuration="@(ResolvedRazorConfiguration)" Extensions="@(ResolvedRazorExtension)" Sources="@(_RazorComponentDeclarationSources)"