Skip to content

Commit

Permalink
feat: Get NetCore Preprocessor Directive, Reference Assembly, and Pac…
Browse files Browse the repository at this point in the history
…kage Identity Dynamically (#584)
  • Loading branch information
BenjaminMichaelis authored Nov 6, 2023
1 parent fbe3d38 commit cdee374
Show file tree
Hide file tree
Showing 20 changed files with 102 additions and 73 deletions.
3 changes: 0 additions & 3 deletions src/Chapter02.Tests/Chapter02.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
<ChapterNumber>02</ChapterNumber>
</PropertyGroup>
<Import Project="..\ChapterTests.props" />
<ItemGroup>
<Compile Include="..\Shared\NetCore.cs" Link="NetCore.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chapter02\Chapter02.csproj" />
</ItemGroup>
Expand Down
21 changes: 21 additions & 0 deletions src/Chapter02.Tests/NetCoreTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

namespace AddisonWesley.Michaelis.EssentialCSharp.Shared.Tests;

[TestClass]
public class NetCoreTests
{
[TestMethod]
public void GetAllValidNETPreprocessorSymbols_ContainsCorrectSymbols()
{
List<string> preprocessorSymbols = NetCore.GetAllValidNETPreprocessorSymbols(8, 0).ToList();
CollectionAssert.Contains(preprocessorSymbols, "NET8_0");
CollectionAssert.Contains(preprocessorSymbols, "NET8_0_OR_GREATER");
CollectionAssert.Contains(preprocessorSymbols, "NET7_0_OR_GREATER");
CollectionAssert.Contains(preprocessorSymbols, "NET6_0_OR_GREATER");
CollectionAssert.Contains(preprocessorSymbols, "NET5_0_OR_GREATER");
CollectionAssert.Contains(preprocessorSymbols, "NETCOREAPP3_1_OR_GREATER");
CollectionAssert.Contains(preprocessorSymbols, "NETCOREAPP3_0_OR_GREATER");

Assert.AreEqual(preprocessorSymbols.Count, preprocessorSymbols.Distinct().Count());
}
}
3 changes: 0 additions & 3 deletions src/Chapter04.Tests/Chapter04.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
<ChapterNumber>04</ChapterNumber>
</PropertyGroup>
<Import Project="..\ChapterTests.props" />
<ItemGroup>
<Compile Include="..\Shared\NetCore.cs" Link="NetCore.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chapter04\Chapter04.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter06.Listing06_39.Tests;

#if NET7_0_OR_GREATER
[TestClass]
public class ProgramTests
{
[TestMethod]
public async Task NotSpecifyingRequiredMembersWithinTheObjectInitializer()
{
#if NET7_0_OR_GREATER
await CompilerAssert.CompileAsync(
new string[]{
CompilerAssert.GetTargetFileNameToCompileFromTestFileName(),
"Listing06.38.RequiredProperties.cs"},
"Listing06.39.SpecifyingRequiredMembersWithinTheObjectInitializer.cs",
"Listing06.38.RequiredProperties.cs" },
new string[] { "CS9035" });
#endif //NET7_0_OR_GREATER
}
}
#endif // NET7_0_OR_GREATER
11 changes: 8 additions & 3 deletions src/Chapter06/Listing06.38.RequiredProperties.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
#if NET7_0_OR_GREATER

namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter06.Listing06_38;

#if NET7_0_OR_GREATER
#region INCLUDE
public class Book
{
public Book()
{
// Look up employee name...
// ...
}

string? _Title;
#region HIGHLIGHT
public required string Title
Expand Down Expand Up @@ -58,4 +63,4 @@ public static void Main()
}
}
#endregion INCLUDE
#endif // NET7_0_OR_GREATER
#endif // NET7_0_OR_GREATER
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
#if NET7_0_OR_GREATER

#if COMPILEERROR // EXCLUDE
using AddisonWesley.Michaelis.EssentialCSharp.Chapter06.Listing06_38;
#endif // COMPILEERROR // EXCLUDE
#if NET7_0_OR_GREATER // EXCLUDE
namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter06.Listing06_39;

using System.Runtime.CompilerServices;

namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter06.Listing06_39;
using Listing06_38;

public class Program
{
public static void Main()
{
#if COMPILEERROR // EXCLUDE
#region INCLUDE
// Error CS9035:
// Required member 'Book.Isbn' must be set in the object
// initializer or attribute constructor
#if COMPILEERROR // EXCLUDE
Book book = new() { Title= "Essential C#" };
#endif // COMPILEERROR // EXCLUDE

// ...
#endregion INCLUDE
#endif // COMPILEERROR // EXCLUDE
}
}
}
#endif // NET7_0_OR_GREATER
#endif // NET7_0_OR_GREATER // EXCLUDE
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public partial class LambdaExpressionNotesAndExamples
// 1.
public static void DiscardParameters()
{
#if !NET6_0_OR_GREATER
#if NET5_0_OR_GREATER
Action<int, int> x = (_, _)=>
Console.WriteLine("This is a test.");
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public partial class LambdaExpressionNotesAndExamples
// 2.
static public void TypeInferenceOfExpression()
{
#if !NET6_0_OR_GREATER
#if NET6_0_OR_GREATER
//You can assign lambda
//expression to an implicitly
//typed local variable starting C#10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public partial class LambdaExpressionNotesAndExamples
// 3.
static public void ExpressionsCanHaveReturnTypes()
{
#if !NET6_0_OR_GREATER
#if NET6_0_OR_GREATER
Action action = void () => { };
var func = short?(long number) =>
number <= short.MaxValue ? (short)number : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ public partial class LambdaExpressionNotesAndExamples
// 4.
public static void MemberMethodsOnExpressions()
{
//#if COMPILEERROR
#if !NET6_0_OR_GREATER
#if COMPILEERROR
//ERROR: Operator "." cannot be applied to
//operand of type "lambda expression"
string s = ((int x) => x).ToString();
#endif
//#endif // COMPILEERROR
#endif // COMPILEERROR
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ public partial class LambdaExpressionNotesAndExamples
// 5.
public static void PatternMatchingOnType()
{
//#if COMPILEERROR
#if !NET6_0_OR_GREATER
#if COMPILEERROR
//ERROR: The first operand of an "is" or "as"
//operator may not be a lambda expression or
//anonymous method
bool b = ((int x) => x) is Func<int,int>;
#endif
//#endif // COMPILEERROR
#endif // COMPILEERROR
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ public partial class LambdaExpressionNotesAndExamples
// 6.
public static void ConvertingToImproperDelegate()
{
//#if COMPILEERROR
#if !NET6_0_OR_GREATER
#if COMPILEERROR
//ERROR: Lambda expression is not compatible
//with Func<int, bool> type
Func<int, bool> f = (int x) => x;
#endif
//#endif // COMPILEERROR
#endif // COMPILEERROR
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ public partial class LambdaExpressionNotesAndExamples
// 7.
public static void JumpStatementsToOutOfScopeDestinations()
{
//#if COMPILEERROR
#if !NET6_0_OR_GREATER
#if COMPILEERROR
//ERROR: Control cannot leave the body of an
//anonymous method or lambda expression
string[] args = { "/File", "fileThatMostCertainlyDoesNotExist" };
Expand All @@ -24,7 +23,6 @@ public static void JumpStatementsToOutOfScopeDestinations()
return args[1];
};
}
#endif
//#endif // COMPILEERROR
#endif // COMPILEERROR
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ public partial class LambdaExpressionNotesAndExamples
// 8.
public static void AccessingParametersAndLocalsOutOfBody()
{
//#if COMPILEERROR
#if !NET6_0_OR_GREATER
#if COMPILEERROR
//ERROR: The name "first" does not
//exist in the current context
Func <int, int, bool> expression =
(first, second) => first > second;
first++;
#endif
//#endif // COMPILEERROR
#endif // COMPILEERROR
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ public partial class LambdaExpressionNotesAndExamples
// 9.
public static void UsingOutParameters()
{
//#if COMPILEERROR
#if !NET6_0_OR_GREATER
#if COMPILEERROR
int number;
Func <string, bool> f =
text => int.TryParse(text, out number);
Expand All @@ -18,7 +17,6 @@ public static void UsingOutParameters()
//ERROR: Use of unassigned local variable
System.Console.Write(number);
}
#endif
//#endif // COMPILEERROR
#endif // COMPILEERROR
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ public partial class LambdaExpressionNotesAndExamples
// 10.
public static void CompilerWillNotDetectInLambdaAssignment()
{
//#if COMPILEERROR
#if !NET6_0_OR_GREATER

#if COMPILEERROR
int number;
Func<int, bool> isFortyTwo =
x => 42 == (number = x);
Expand All @@ -19,7 +17,6 @@ public static void CompilerWillNotDetectInLambdaAssignment()
// ERROR: Use of unassigned local variable
System.Console.Write(number);
}
#endif
//#endif // COMPILEERROR
#endif // COMPILEERROR
}
}
3 changes: 0 additions & 3 deletions src/Chapter16.Tests/Chapter16.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
<ChapterNumber>16</ChapterNumber>
</PropertyGroup>
<Import Project="..\ChapterTests.props" />
<ItemGroup>
<Compile Include="..\Shared\NetCore.cs" Link="NetCore.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chapter16\Chapter16.csproj" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/ChapterTests.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting"/>
<Compile Include="..\Shared\Tests\CompilerAssert.cs" Link="CompilerAssert.cs" />
<Compile Include="..\Shared\NetCore.cs" Link="NetCore.cs" />
</ItemGroup>

<!-- TODO: Ideally this would be placed in a ChapterTests.targets file. -->
Expand Down
43 changes: 39 additions & 4 deletions src/Shared/NetCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,48 @@ namespace AddisonWesley.Michaelis.EssentialCSharp.Shared;

public static class NetCore
{
private static readonly char[] _Separators = new[] { '/', '\\' };

public static string GetNetCoreVersion()
{
Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
string[] assemblyPath = assembly.Location!.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
string[] assemblyPath = assembly.Location!.Split(_Separators, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];
throw new Exception("Unable to determine .NET Core version.");
return netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2
? assemblyPath[netCoreAppIndex + 1]
: throw new InvalidOperationException("Unable to determine .NET Core version.");
}

public static string GetCurrentNETPreprocessorSymbol()
{
return $"NET{Environment.Version.Major}_{Environment.Version.Minor}_OR_GREATER";
}

public static IEnumerable<string> GetAllValidNETPreprocessorSymbols()
{
Version version = Environment.Version;
return GetAllValidNETPreprocessorSymbols(version.Major, version.Minor);
}

public static IEnumerable<string> GetAllValidNETPreprocessorSymbols(int majorVersion, int minorVersion)
{
// TODO: Handle minor versions better
// TODO: Handle versions that aren't NET 5.0 or later
yield return $"NET{majorVersion}_{minorVersion}";

// before net 5.0, precompile symbol is NETCOREAPP
for (int i = 5; i <= majorVersion; i++)
{
yield return $"NET{i}_0_OR_GREATER";
}

// ex: NETCOREAPP3_1_OR_GREATER
for (int i = 1; i < 4; i++)
{
for (int j = 0; j <= 1; j++)
{
yield return $"NETCOREAPP{i}_{j}_OR_GREATER";
}
}
}
}
15 changes: 5 additions & 10 deletions src/Shared/Tests/CompilerAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ public static string GetTargetFileNameToCompileFromTestFileName(
public static async Task CompileAsync(string[] fileNames, string[] expectedErrorIds)
{

var test = new Test()
Test test = new()
{
CompilerDiagnostics = CompilerDiagnostics.Warnings,
ReferenceAssemblies = new ReferenceAssemblies(
"net7.0",
$"net{Environment.Version.Major}.{Environment.Version.Minor}",
new PackageIdentity(
"Microsoft.NETCore.App.Ref",
"7.0.0"),
Path.Combine("ref", "net7.0"))
NetCore.GetNetCoreVersion()),
Path.Combine("ref", $"net{Environment.Version.Major}.{Environment.Version.Minor}"))
};

List<string> fileNamesToCompile = new();
Expand Down Expand Up @@ -166,12 +166,7 @@ private static ImmutableDictionary<string, ReportDiagnostic> GetNullableWarnings

protected override ParseOptions CreateParseOptions()
=> new CSharpParseOptions(LanguageVersion, DocumentationMode.Diagnose)
#if NET7_0_OR_GREATER
.WithPreprocessorSymbols("COMPILEERROR", "NET7_0_OR_GREATER")
#else
.WithPreprocessorSymbols("COMPILEERROR")
#endif // NET7_0_OR_GREATER
;
.WithPreprocessorSymbols(NetCore.GetAllValidNETPreprocessorSymbols().Append("COMPILEERROR"));

protected override IEnumerable<DiagnosticAnalyzer> GetDiagnosticAnalyzers()
=> Enumerable.Empty<DiagnosticAnalyzer>();
Expand Down

0 comments on commit cdee374

Please sign in to comment.