diff --git a/.vscode/settings.json b/.vscode/settings.json
index fd80bb6e..804aa441 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -11,7 +11,9 @@
}
],
"cSpell.words": [
+ "langversion",
"Preprocess",
- "reimplementation"
+ "reimplementation",
+ "Xunit"
]
}
diff --git a/Directory.Build.props b/Directory.Build.props
index 7162a681..33b96ee4 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -17,25 +17,13 @@
+
-
-
- true
- true
- embedded
-
-
- true
-
-
-
-
-
true
$(MSBuildThisFileDirectory)TextTemplating.snk
diff --git a/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs b/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs
index 6e875b71..b3323d55 100644
--- a/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs
+++ b/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs
@@ -1,6 +1,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
@@ -37,11 +38,13 @@ CodeCompilerResult CompileFileInternal (
CancellationToken token)
{
CSharpCommandLineArguments args = null;
+ bool hasLangVersionArg = false;
if (arguments.AdditionalArguments != null) {
var splitArgs = CommandLineParser.SplitCommandLineIntoArguments (arguments.AdditionalArguments, false);
if (splitArgs.Any ()) {
args = CSharpCommandLineParser.Default.Parse (splitArgs, arguments.TempDirectory, null, null);
}
+ hasLangVersionArg = splitArgs.Any (CSharpLangVersionHelper.IsLangVersionArg);
}
var references = new List ();
@@ -51,15 +54,31 @@ CodeCompilerResult CompileFileInternal (
var parseOptions = args?.ParseOptions ?? new CSharpParseOptions();
+ // arguments.LangVersion takes precedence over any langversion arg in arguments.AdditionalArguments
+ // This behavior should match that of CscCodeCompiler.CompileFile and CSharpLangVersionHelper.GetLangVersionArg
if (arguments.LangVersion != null) {
if (LanguageVersionFacts.TryParse(arguments.LangVersion, out var langVersion)) {
parseOptions = parseOptions.WithLanguageVersion (langVersion);
+ hasLangVersionArg = true;
} else {
throw new RoslynCodeCompilerException ($"Unknown value '{arguments.LangVersion}' for langversion");
}
- } else {
- // need to update this when updating referenced roslyn binaries
- CSharpLangVersionHelper.GetBestSupportedLangVersion (runtime, CSharpLangVersion.v9_0);
+ }
+
+ if (!hasLangVersionArg) {
+ // Default to the highest language version supported by the runtime
+ // as we may be using a version of Roslyn where "latest" language
+ // features depend on new APIs that aren't available on the current runtime.
+ // If the runtime is an unknown version, its MaxSupportedLangVersion will default
+ // to "latest" so new runtime versions will work before we explicitly add support for them.
+ if (LanguageVersionFacts.TryParse (CSharpLangVersionHelper.ToString (runtime.MaxSupportedLangVersion), out var runtimeSupportedLangVersion)) {
+ parseOptions = parseOptions.WithLanguageVersion (runtimeSupportedLangVersion);
+ } else {
+ // if Roslyn did not recognize the runtime's default lang version, it's newer than
+ // this version of Roslyn supports, so default to the latest supported version
+ parseOptions = parseOptions.WithLanguageVersion (LanguageVersion.Latest);
+ }
+
}
var syntaxTrees = new List ();
@@ -102,7 +121,7 @@ CodeCompilerResult CompileFileInternal (
var startLinePosition = location.StartLinePosition;
var endLinePosition = location.EndLinePosition;
return new CodeCompilerError {
- Message = x.GetMessage (),
+ Message = x.GetMessage (CultureInfo.CurrentCulture),
Column = startLinePosition.Character,
Line = startLinePosition.Line,
EndLine = endLinePosition.Line,
diff --git a/Mono.TextTemplating.Tests/ProcessingTests.cs b/Mono.TextTemplating.Tests/ProcessingTests.cs
index 58b5a4f1..2cb8b752 100644
--- a/Mono.TextTemplating.Tests/ProcessingTests.cs
+++ b/Mono.TextTemplating.Tests/ProcessingTests.cs
@@ -67,7 +67,26 @@ public async Task CSharp9Records ()
#endif
}
-#if !NET472
+ [Fact]
+ public async Task CSharp11StructRecords ()
+ {
+ string template = "<#+ public record struct Foo(string bar); #>";
+ var gen = new TemplateGenerator ();
+ string outputName = null;
+ await gen.ProcessTemplateAsync (null, template, outputName);
+
+ CompilerError firstError = gen.Errors.OfType ().FirstOrDefault ();
+
+ // note: when running on netsdk we use the highest available csc regardless of runtime version,
+ // so struct records will always be available on our test environments
+#if NETFRAMEWORK
+ Assert.NotNull (firstError);
+#else
+ Assert.Null (firstError);
+#endif
+ }
+
+#if !NETFRAMEWORK
[Fact]
public async Task SetLangVersionViaAttribute ()
{
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersion.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersion.cs
new file mode 100644
index 00000000..36b3c00d
--- /dev/null
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersion.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if NETFRAMEWORK
+#nullable enable annotations
+#else
+#nullable enable
+#endif
+
+namespace Mono.TextTemplating.CodeCompilation;
+
+enum CSharpLangVersion
+{
+ v5_0,
+ v6_0,
+ v7_0,
+ v7_1,
+ v7_2,
+ v7_3,
+ v8_0,
+ v9_0,
+ v10_0,
+ v11_0,
+ v12_0,
+ Latest = 1024 // make sure value doesn't change as we add new C# versions
+}
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs
index 20582dad..03395756 100644
--- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs
@@ -1,90 +1,79 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if NETFRAMEWORK
+#nullable enable annotations
+#else
+#nullable enable
+#endif
+
using System;
-using System.Collections.Generic;
using System.Linq;
-namespace Mono.TextTemplating.CodeCompilation
-{
- enum CSharpLangVersion
- {
- v5_0,
- v6_0,
- v7_0,
- v7_1,
- v7_2,
- v7_3,
- v8_0,
- v9_0,
- Latest
- }
-
- static class CSharpLangVersionHelper
- {
- public static CSharpLangVersion GetBestSupportedLangVersion (RuntimeInfo runtime, CSharpLangVersion? compilerLangVersion = null)
- => (CSharpLangVersion)Math.Min ((int)(compilerLangVersion ?? runtime.MaxSupportedLangVersion), (int) (runtime switch {
- { Kind: RuntimeKind.NetCore, Version.Major: > 5 } => CSharpLangVersion.Latest,
- { Kind: RuntimeKind.NetCore, Version.Major: 5 } => CSharpLangVersion.v9_0,
- { Kind: RuntimeKind.NetCore, Version.Major: 3 } => CSharpLangVersion.v8_0,
- _ => CSharpLangVersion.v7_3,
- }));
-
- static bool HasLangVersionArg (string args) =>
- !string.IsNullOrEmpty(args)
- && (args.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) > -1)
- && ProcessArgumentBuilder.TryParse (args, out var parsedArgs)
- && parsedArgs.Any (a => a.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) == 1);
+namespace Mono.TextTemplating.CodeCompilation;
- static string ToString (CSharpLangVersion version) => version switch {
- CSharpLangVersion.v5_0 => "5",
- CSharpLangVersion.v6_0 => "6",
- CSharpLangVersion.v7_0 => "7",
- CSharpLangVersion.v7_1 => "7.1",
- CSharpLangVersion.v7_2 => "7.2",
- CSharpLangVersion.v7_3 => "7.3",
- CSharpLangVersion.v8_0 => "8.0",
- CSharpLangVersion.v9_0 => "9.0",
- CSharpLangVersion.Latest => "latest",
- _ => throw new ArgumentException ($"Not a valid value: '{version}'", nameof (version))
- };
-
- public static string GetLangVersionArg (CodeCompilerArguments arguments, RuntimeInfo runtime)
- {
- if (!string.IsNullOrWhiteSpace (arguments.LangVersion)) {
- return $"-langversion:{arguments.LangVersion}";
- }
+static class CSharpLangVersionHelper
+{
+ public static bool HasLangVersionArg (string args) =>
+ !string.IsNullOrEmpty(args)
+ && (args.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) > -1)
+ && ProcessArgumentBuilder.TryParse (args, out var parsedArgs)
+ && parsedArgs.Any (IsLangVersionArg);
- if (HasLangVersionArg (arguments.AdditionalArguments)) {
- return null;
- }
+ public static bool IsLangVersionArg (string arg) =>
+ (arg[0] == '-' || arg[0] == '/')
+ && arg.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) == 1;
- return $"-langversion:{ToString(GetBestSupportedLangVersion(runtime))}";
+ public static string? GetLangVersionArg (CodeCompilerArguments arguments, RuntimeInfo runtime)
+ {
+ // Arguments.LangVersion takes precedence over -langversion in arguments.AdditionalArguments.
+ // This behavior should match that of CscCodeCompiler.CompileFile and RoslynCodeCompiler.CompileFileInternal
+ if (!string.IsNullOrWhiteSpace (arguments.LangVersion)) {
+ return $"-langversion:{arguments.LangVersion}";
}
- public static CSharpLangVersion? FromRoslynPackageVersion (string roslynPackageVersion)
- => SemVersion.TryParse (roslynPackageVersion, out var version)
- ? version switch {
- { Major: > 3 } => CSharpLangVersion.v9_0,
- { Major: 3, Minor: >= 8 } => CSharpLangVersion.v9_0,
- { Major: 3, Minor: >= 3 } => CSharpLangVersion.v8_0,
- // ignore 8.0 preview support in 3.0-3.2
- { Major: 2, Minor: >= 8 } => CSharpLangVersion.v7_3,
- { Major: 2, Minor: >= 6 } => CSharpLangVersion.v7_2,
- { Major: 2, Minor: >= 3 } => CSharpLangVersion.v7_1,
- { Major: 2 } => CSharpLangVersion.v7_0,
- _ => CSharpLangVersion.v6_0
- }
- : null;
+ if (HasLangVersionArg (arguments.AdditionalArguments)) {
+ return null;
+ }
- //https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history
- public static CSharpLangVersion FromNetCoreSdkVersion (SemVersion sdkVersion)
- => sdkVersion switch {
- { Major: >= 5 } => CSharpLangVersion.v9_0,
- { Major: 3 } => CSharpLangVersion.v8_0,
- { Major: 2, Minor: >= 1 } => CSharpLangVersion.v7_3,
- { Major: 2, Minor: >= 0 } => CSharpLangVersion.v7_1,
- _ => CSharpLangVersion.v7_0
- };
+ // Default to the highest language version supported by the runtime
+ // as we may be using a csc from a newer runtime where "latest" language
+ // features depend on new APIs that aren't available on the current runtime.
+ // If we were unable to determine the supported language version for the runtime,
+ // its MaxSupportedLangVersion will default to "Latest" so its language features
+ // are available before we add a language version mapping for that runtime version.
+ return $"-langversion:{ToString (runtime.MaxSupportedLangVersion)}";
}
+
+ //https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history
+ public static CSharpLangVersion FromNetCoreSdkVersion (SemVersion sdkVersion)
+ => sdkVersion switch {
+ { Major: 8 } => CSharpLangVersion.v12_0,
+ { Major: 7 } => CSharpLangVersion.v11_0,
+ { Major: 6 } => CSharpLangVersion.v10_0,
+ { Major: 5 } => CSharpLangVersion.v9_0,
+ { Major: 3 } => CSharpLangVersion.v8_0,
+ { Major: 2, Minor: >= 1 } => CSharpLangVersion.v7_3,
+ { Major: 2, Minor: >= 0 } => CSharpLangVersion.v7_1,
+ { Major: 1 } => CSharpLangVersion.v7_1,
+ // for unknown versions, always fall through to "Latest" so we don't break the
+ // ability to use new C# versions as they are released
+ _ => CSharpLangVersion.Latest
+ };
+
+ public static string ToString (CSharpLangVersion version) => version switch {
+ CSharpLangVersion.v5_0 => "5",
+ CSharpLangVersion.v6_0 => "6",
+ CSharpLangVersion.v7_0 => "7",
+ CSharpLangVersion.v7_1 => "7.1",
+ CSharpLangVersion.v7_2 => "7.2",
+ CSharpLangVersion.v7_3 => "7.3",
+ CSharpLangVersion.v8_0 => "8.0",
+ CSharpLangVersion.v9_0 => "9.0",
+ CSharpLangVersion.v10_0 => "10.0",
+ CSharpLangVersion.v11_0 => "11.0",
+ CSharpLangVersion.v12_0 => "12.0",
+ CSharpLangVersion.Latest => "latest",
+ _ => throw new ArgumentException ($"Not a valid value: '{version}'", nameof (version))
+ };
}
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs
index 1abecd17..00858b2a 100644
--- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs
@@ -24,6 +24,11 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+#if NETFRAMEWORK
+#nullable enable annotations
+#else
+#nullable enable
+#endif
using System;
using System.Collections.Generic;
@@ -45,21 +50,19 @@ public CscCodeCompiler (RuntimeInfo runtime)
static StreamWriter CreateTempTextFile (string extension, out string path)
{
- path = null;
- Exception ex = null;
try {
var tempDir = Path.GetTempPath ();
Directory.CreateDirectory (tempDir);
//this is how msbuild does it...
- path = Path.Combine (tempDir, $"tmp{Guid.NewGuid ():N}{extension}");
+ path = Path.Combine (tempDir, $"tmp{Guid.NewGuid ():N}{extension}")!;
if (!File.Exists (path)) {
return File.CreateText (path);
}
- } catch (Exception e) {
- ex = e;
+ } catch (Exception ex) {
+ throw new TemplatingEngineException ("Failed to create temp file", ex);
}
- throw new TemplatingEngineException ("Failed to create temp file", ex);
+ throw new TemplatingEngineException ("Failed to create temp file");
}
///
@@ -70,7 +73,7 @@ static StreamWriter CreateTempTextFile (string extension, out string path)
/// Token.
public override async Task CompileFile (CodeCompilerArguments arguments, TextWriter log, CancellationToken token)
{
- string rspPath;
+ string? rspPath;
StreamWriter rsp;
if (arguments.TempDirectory != null) {
rspPath = Path.Combine (arguments.TempDirectory, "response.rsp");
@@ -86,11 +89,6 @@ public override async Task CompileFile (CodeCompilerArgument
rsp.WriteLine ("-debug");
}
- var langVersionArg = CSharpLangVersionHelper.GetLangVersionArg (arguments, runtime);
- if (langVersionArg != null) {
- rsp.WriteLine (langVersionArg);
- }
-
foreach (var reference in AssemblyResolver.GetResolvedReferences (runtime, arguments.AssemblyReferences)) {
rsp.Write ("-r:");
rsp.Write ("\"");
@@ -107,6 +105,15 @@ public override async Task CompileFile (CodeCompilerArgument
rsp.WriteLine (arguments.AdditionalArguments);
}
+ // This comes after AdditionalArguments so arguments.LangVersion will take precedence
+ // over any langversion arg in AdditionalArguments.
+ // This behavior should match that of CSharpLangVersionHelper.GetLangVersionArg and
+ // RoslynCodeCompiler.CompileFileInternal
+ var langVersionArg = CSharpLangVersionHelper.GetLangVersionArg (arguments, runtime);
+ if (langVersionArg != null) {
+ rsp.WriteLine (langVersionArg);
+ }
+
//in older versions of csc, these must come last
foreach (var file in arguments.SourceFiles) {
rsp.Write ("\"");
@@ -151,8 +158,7 @@ public override async Task CompileFile (CodeCompilerArgument
void ConsumeOutput (string s)
{
using var sw = new StringReader (s);
- string line;
- while ((line = sw.ReadLine ()) != null) {
+ while (sw.ReadLine () is string line) {
outputList.Add (line);
var err = MSBuildErrorParser.TryParseLine (line);
if (err != null) {
@@ -197,7 +203,7 @@ public override void WriteLine ()
b.WriteLine ();
}
- public override void Write (string value)
+ public override void Write (string? value)
{
a.Write (value);
b.Write (value);
diff --git a/Mono.TextTemplating/Mono.TextTemplating.csproj b/Mono.TextTemplating/Mono.TextTemplating.csproj
index a47b47a0..25aab184 100644
--- a/Mono.TextTemplating/Mono.TextTemplating.csproj
+++ b/Mono.TextTemplating/Mono.TextTemplating.csproj
@@ -16,6 +16,8 @@
2.2.1
readme.md
en-US
+
+ $(NoWarn);NU5129
@@ -23,9 +25,9 @@
-
+
-
+