Skip to content

Commit 6e5b28c

Browse files
authored
Merge pull request #136 from feO2x/135-source-code-transformation-for-dotnet-8
Source Code Transformation for .NET 8
2 parents 27c6e95 + 454426e commit 6e5b28c

17 files changed

Lines changed: 432 additions & 334 deletions

Code/Light.GuardClauses.AllProjects.sln

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceCodePublishing", "Sou
2121
EndProject
2222
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Light.GuardClauses.SourceCodeTransformation", "Light.GuardClauses.SourceCodeTransformation\Light.GuardClauses.SourceCodeTransformation.csproj", "{ECE42390-E3B5-49D4-9452-3B0CC04C633A}"
2323
EndProject
24-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Light.GuardClauses.Source", "Light.GuardClauses.Source\Light.GuardClauses.Source.csproj", "{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}"
24+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Light.GuardClauses.SourceValidation", "Light.GuardClauses.SourceValidation\Light.GuardClauses.SourceValidation.csproj", "{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}"
2525
EndProject
2626
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Light.GuardClauses.SourceCodeTransformation.Tests", "Light.GuardClauses.SourceCodeTransformation.Tests\Light.GuardClauses.SourceCodeTransformation.Tests.csproj", "{A2067796-F167-47BE-B367-191152DCE230}"
2727
EndProject
@@ -118,18 +118,18 @@ Global
118118
{ECE42390-E3B5-49D4-9452-3B0CC04C633A}.Release|x64.Build.0 = Release|Any CPU
119119
{ECE42390-E3B5-49D4-9452-3B0CC04C633A}.Release|x86.ActiveCfg = Release|Any CPU
120120
{ECE42390-E3B5-49D4-9452-3B0CC04C633A}.Release|x86.Build.0 = Release|Any CPU
121-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
122-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Debug|Any CPU.Build.0 = Debug|Any CPU
123-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Debug|x64.ActiveCfg = Debug|Any CPU
124-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Debug|x64.Build.0 = Debug|Any CPU
125-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Debug|x86.ActiveCfg = Debug|Any CPU
126-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Debug|x86.Build.0 = Debug|Any CPU
127-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Release|Any CPU.ActiveCfg = Release|Any CPU
128-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Release|Any CPU.Build.0 = Release|Any CPU
129-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Release|x64.ActiveCfg = Release|Any CPU
130-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Release|x64.Build.0 = Release|Any CPU
131-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Release|x86.ActiveCfg = Release|Any CPU
132-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843}.Release|x86.Build.0 = Release|Any CPU
121+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
122+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
123+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Debug|x64.ActiveCfg = Debug|Any CPU
124+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Debug|x64.Build.0 = Debug|Any CPU
125+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Debug|x86.ActiveCfg = Debug|Any CPU
126+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Debug|x86.Build.0 = Debug|Any CPU
127+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
128+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Release|Any CPU.Build.0 = Release|Any CPU
129+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Release|x64.ActiveCfg = Release|Any CPU
130+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Release|x64.Build.0 = Release|Any CPU
131+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Release|x86.ActiveCfg = Release|Any CPU
132+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF}.Release|x86.Build.0 = Release|Any CPU
133133
{A2067796-F167-47BE-B367-191152DCE230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
134134
{A2067796-F167-47BE-B367-191152DCE230}.Debug|Any CPU.Build.0 = Debug|Any CPU
135135
{A2067796-F167-47BE-B367-191152DCE230}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -150,7 +150,7 @@ Global
150150
{DB4A3982-5312-4923-9C79-C39D18A5899C} = {A58A06DD-EEDC-4B1B-830F-0927739CFB9E}
151151
{2FE2D52E-21B8-4C51-9983-9C1812BDEBA0} = {A58A06DD-EEDC-4B1B-830F-0927739CFB9E}
152152
{ECE42390-E3B5-49D4-9452-3B0CC04C633A} = {0E0A86F9-C976-4FA1-8BF6-3A6A21D86BFD}
153-
{A7B9AC78-FA4F-4BDE-9DAF-B854408AB843} = {0E0A86F9-C976-4FA1-8BF6-3A6A21D86BFD}
153+
{B546B7B7-9CA5-456C-96CD-15C21E64B7DF} = {0E0A86F9-C976-4FA1-8BF6-3A6A21D86BFD}
154154
{A2067796-F167-47BE-B367-191152DCE230} = {0E0A86F9-C976-4FA1-8BF6-3A6A21D86BFD}
155155
EndGlobalSection
156156
GlobalSection(ExtensibilityGlobals) = postSolution

Code/Light.GuardClauses.Source/Light.GuardClauses.Source.csproj

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 16 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.IO;
1+
using System.IO;
32
using FluentAssertions;
43
using Xunit;
54

@@ -8,25 +7,16 @@ namespace Light.GuardClauses.SourceCodeTransformation.Tests;
87
public static class GeneratedFileBuildValidatorTests
98
{
109
[Fact]
11-
public static void ValidateBuildsSingleProjectNextToGeneratedFile()
10+
public static void TryFindSourceValidationProjectFindsProjectFromRepositoryRoot()
1211
{
13-
using var temporaryDirectory = new TemporaryDirectory();
14-
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Generated.cs");
15-
File.WriteAllText(targetFile, "namespace ValidationSample; public static class Generated { }");
16-
File.WriteAllText(
17-
Path.Combine(temporaryDirectory.DirectoryPath, "ValidationSample.csproj"),
18-
"""
19-
<Project Sdk="Microsoft.NET.Sdk">
20-
<PropertyGroup>
21-
<TargetFramework>net8.0</TargetFramework>
22-
<ImplicitUsings>enable</ImplicitUsings>
23-
<Nullable>enable</Nullable>
24-
</PropertyGroup>
25-
</Project>
26-
"""
12+
var repositoryRoot = TestEnvironment.CodeDirectory.Parent!.FullName;
13+
var expectedProjectPath = Path.Combine(
14+
TestEnvironment.CodeDirectory.FullName,
15+
"Light.GuardClauses.SourceValidation",
16+
"Light.GuardClauses.SourceValidation.csproj"
2717
);
2818

29-
GeneratedFileBuildValidator.Validate(targetFile).Should().Be(0);
19+
GeneratedFileBuildValidator.TryFindSourceValidationProject([repositoryRoot]).Should().Be(expectedProjectPath);
3020
}
3121

3222
[Fact]
@@ -35,55 +25,36 @@ public static void ValidateReturnsNonZeroExitCodeAndLeavesFileOnBuildFailure()
3525
using var temporaryDirectory = new TemporaryDirectory();
3626
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Generated.cs");
3727
File.WriteAllText(targetFile, "namespace ValidationSample; public static class Generated { this is invalid }");
38-
File.WriteAllText(
39-
Path.Combine(temporaryDirectory.DirectoryPath, "ValidationSample.csproj"),
40-
"""
41-
<Project Sdk="Microsoft.NET.Sdk">
42-
<PropertyGroup>
43-
<TargetFramework>net8.0</TargetFramework>
44-
</PropertyGroup>
45-
</Project>
46-
"""
47-
);
4828

49-
GeneratedFileBuildValidator.Validate(targetFile).Should().NotBe(0);
29+
GeneratedFileBuildValidator.Validate(SourceTargetFramework.Net8_0, targetFile).Should().NotBe(0);
5030
File.Exists(targetFile).Should().BeTrue();
5131
}
5232

5333
[Fact]
54-
public static void ValidateSkipsWhenNoProjectFileExists()
34+
public static void ValidateReturnsZeroForCompilingFileOnNet8()
5535
{
5636
using var temporaryDirectory = new TemporaryDirectory();
5737
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Generated.cs");
5838
File.WriteAllText(targetFile, "namespace ValidationSample; public static class Generated { }");
5939

60-
GeneratedFileBuildValidator.Validate(targetFile).Should().Be(0);
40+
GeneratedFileBuildValidator.Validate(SourceTargetFramework.Net8_0, targetFile).Should().Be(0);
6141
}
6242

6343
[Fact]
64-
public static void ValidateSkipsWhenMultipleProjectFilesExist()
44+
public static void ValidateReturnsZeroForCompilingFileOnNetStandard()
6545
{
6646
using var temporaryDirectory = new TemporaryDirectory();
6747
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Generated.cs");
6848
File.WriteAllText(targetFile, "namespace ValidationSample; public static class Generated { }");
69-
File.WriteAllText(
70-
Path.Combine(temporaryDirectory.DirectoryPath, "First.csproj"),
71-
"<Project Sdk=\"Microsoft.NET.Sdk\" />"
72-
);
73-
File.WriteAllText(
74-
Path.Combine(temporaryDirectory.DirectoryPath, "Second.csproj"),
75-
"<Project Sdk=\"Microsoft.NET.Sdk\" />"
76-
);
7749

78-
GeneratedFileBuildValidator.Validate(targetFile).Should().Be(0);
50+
GeneratedFileBuildValidator.Validate(SourceTargetFramework.NetStandard2_0, targetFile).Should().Be(0);
7951
}
8052

8153
[Fact]
82-
public static void ProgramRunsNonWhitelistTransformationAndSkipsValidationWithoutProject()
54+
public static void ProgramRunsNonWhitelistNetStandardTransformationAndValidates()
8355
{
8456
using var temporaryDirectory = new TemporaryDirectory();
85-
var codeDirectory = FindCodeDirectory();
86-
var sourceFolder = Path.Combine(codeDirectory.FullName, "Light.GuardClauses");
57+
var sourceFolder = TestEnvironment.SourceDirectory.FullName;
8758
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Generated.cs");
8859

8960
Program.Main(
@@ -92,46 +63,10 @@ public static void ProgramRunsNonWhitelistTransformationAndSkipsValidationWithou
9263
$"TargetFile={targetFile}",
9364
"IncludeVersionComment=false",
9465
"AssertionWhitelist:IsEnabled=false",
66+
"TargetFramework=NetStandard2_0",
9567
]
9668
).Should().Be(0);
9769

9870
File.Exists(targetFile).Should().BeTrue();
9971
}
100-
101-
private static DirectoryInfo FindCodeDirectory()
102-
{
103-
var currentDirectory = new DirectoryInfo(".");
104-
do
105-
{
106-
if (currentDirectory.Name == "Code")
107-
{
108-
return currentDirectory;
109-
}
110-
111-
currentDirectory = currentDirectory.Parent;
112-
} while (currentDirectory != null);
113-
114-
throw new InvalidOperationException(
115-
"This test project does not reside in a folder called \"Code\" (directly or indirectly)."
116-
);
117-
}
118-
119-
private sealed class TemporaryDirectory : IDisposable
120-
{
121-
public TemporaryDirectory()
122-
{
123-
DirectoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
124-
Directory.CreateDirectory(DirectoryPath);
125-
}
126-
127-
public string DirectoryPath { get; }
128-
129-
public void Dispose()
130-
{
131-
if (Directory.Exists(DirectoryPath))
132-
{
133-
Directory.Delete(DirectoryPath, true);
134-
}
135-
}
136-
}
13772
}
Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.IO;
32
using FluentAssertions;
43
using Microsoft.CodeAnalysis.CSharp;
@@ -9,10 +8,6 @@ namespace Light.GuardClauses.SourceCodeTransformation.Tests;
98

109
public static class ParseCSharpWithRoslynTests
1110
{
12-
private static readonly DirectoryInfo CodeDirectory;
13-
14-
static ParseCSharpWithRoslynTests() => CodeDirectory = FindCodeDirectory();
15-
1611
[Fact]
1712
public static void ParseExpressionExtensionsFile()
1813
{
@@ -36,25 +31,7 @@ public static void ParseSpanDelegatesFile()
3631
root.Members.Should().NotBeEmpty();
3732
}
3833

39-
private static DirectoryInfo FindCodeDirectory()
40-
{
41-
var currentDirectory = new DirectoryInfo(".");
42-
do
43-
{
44-
if (currentDirectory.Name == "Code")
45-
{
46-
return currentDirectory;
47-
}
48-
49-
currentDirectory = currentDirectory.Parent;
50-
} while (currentDirectory != null);
51-
52-
throw new InvalidOperationException(
53-
"This test project does not reside in a folder called \"Code\" (directly or indirectly)."
54-
);
55-
}
56-
5734
private static FileInfo GetLightGuardClausesFile(string relativeFilePath) => new (
58-
Path.Combine(CodeDirectory.FullName, "Light.GuardClauses", relativeFilePath)
35+
Path.Combine(TestEnvironment.SourceDirectory.FullName, relativeFilePath)
5936
);
6037
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.IO;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace Light.GuardClauses.SourceCodeTransformation.Tests;
7+
8+
public static class SourceFileMergerFrameworkTests
9+
{
10+
[Fact]
11+
public static void Net8ExportContainsNet8MembersAndNoDirectives()
12+
{
13+
using var temporaryDirectory = new TemporaryDirectory();
14+
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Net8Export.cs");
15+
16+
SourceFileMerger.CreateSingleSourceFile(CreateOptions(targetFile, SourceTargetFramework.Net8_0));
17+
var sourceCode = File.ReadAllText(targetFile);
18+
19+
sourceCode.Should().Contain("using System.Numerics;");
20+
sourceCode.Should().Contain("INumber<T>");
21+
sourceCode.Should().Contain("MustBeGreaterThanOrApproximately<T>");
22+
sourceCode.Should().Contain("public static ReadOnlySpan<char> MustBeEmailAddress");
23+
sourceCode.Should().Contain("public static Span<char> MustBeEmailAddress");
24+
sourceCode.Should().Contain("public static Memory<char> MustBeEmailAddress");
25+
sourceCode.Should().NotContain("#if");
26+
}
27+
28+
[Fact]
29+
public static void NetStandardExportOmitsNet8MembersAndNoDirectives()
30+
{
31+
using var temporaryDirectory = new TemporaryDirectory();
32+
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "NetStandardExport.cs");
33+
34+
SourceFileMerger.CreateSingleSourceFile(CreateOptions(targetFile, SourceTargetFramework.NetStandard2_0));
35+
var sourceCode = File.ReadAllText(targetFile);
36+
37+
sourceCode.Should().NotContain("using System.Numerics;");
38+
sourceCode.Should().NotContain("INumber<T>");
39+
sourceCode.Should().NotContain("ReadOnlySpan<char> MustBeEmailAddress");
40+
sourceCode.Should().NotContain("Span<char> MustBeEmailAddress");
41+
sourceCode.Should().NotContain("Memory<char> MustBeEmailAddress");
42+
sourceCode.Should().NotContain("#if");
43+
}
44+
45+
[Fact]
46+
public static void Net8ExportOmitsPolyfillAttributes()
47+
{
48+
using var temporaryDirectory = new TemporaryDirectory();
49+
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Net8Polyfill.cs");
50+
51+
SourceFileMerger.CreateSingleSourceFile(CreateOptions(targetFile, SourceTargetFramework.Net8_0));
52+
var sourceCode = File.ReadAllText(targetFile);
53+
54+
sourceCode.Should().NotContain("class AllowNullAttribute");
55+
sourceCode.Should().NotContain("class CallerArgumentExpressionAttribute");
56+
}
57+
58+
[Fact]
59+
public static void NetStandardExportIncludesPolyfillAttributes()
60+
{
61+
using var temporaryDirectory = new TemporaryDirectory();
62+
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "NetStandardPolyfill.cs");
63+
64+
SourceFileMerger.CreateSingleSourceFile(CreateOptions(targetFile, SourceTargetFramework.NetStandard2_0));
65+
var sourceCode = File.ReadAllText(targetFile);
66+
67+
sourceCode.Should().Contain("class AllowNullAttribute");
68+
sourceCode.Should().Contain("class CallerArgumentExpressionAttribute");
69+
}
70+
71+
[Fact]
72+
public static void Net8ExportValidatesAgainstNet8()
73+
{
74+
using var temporaryDirectory = new TemporaryDirectory();
75+
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Net8Validation.cs");
76+
77+
SourceFileMerger.CreateSingleSourceFile(CreateOptions(targetFile, SourceTargetFramework.Net8_0));
78+
79+
GeneratedFileBuildValidator.Validate(SourceTargetFramework.Net8_0, targetFile).Should().Be(0);
80+
}
81+
82+
[Fact]
83+
public static void NetStandardExportValidatesAgainstNetStandard()
84+
{
85+
using var temporaryDirectory = new TemporaryDirectory();
86+
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "NetStandardValidation.cs");
87+
88+
SourceFileMerger.CreateSingleSourceFile(CreateOptions(targetFile, SourceTargetFramework.NetStandard2_0));
89+
90+
GeneratedFileBuildValidator.Validate(SourceTargetFramework.NetStandard2_0, targetFile).Should().Be(0);
91+
}
92+
93+
[Fact]
94+
public static void SyntheticAssertionFileWithNoWhitelistPropertyThrows()
95+
{
96+
using var temporaryDirectory = new TemporaryDirectory();
97+
var sourceFolder = Path.Combine(temporaryDirectory.DirectoryPath, "Source");
98+
Directory.CreateDirectory(sourceFolder);
99+
File.WriteAllText(
100+
Path.Combine(sourceFolder, "Check.SyntheticNotWhitelisted.cs"),
101+
"""
102+
namespace Light.GuardClauses;
103+
104+
public static partial class Check
105+
{
106+
public static T SyntheticNotWhitelisted<T>(T parameter) => parameter;
107+
}
108+
"""
109+
);
110+
var targetFile = Path.Combine(temporaryDirectory.DirectoryPath, "Synthetic.cs");
111+
var options = new SourceFileMergeOptions
112+
{
113+
SourceFolder = sourceFolder,
114+
TargetFile = targetFile,
115+
TargetFramework = SourceTargetFramework.NetStandard2_0,
116+
IncludeVersionComment = false,
117+
AssertionWhitelist = new() { IsEnabled = true },
118+
};
119+
120+
var act = () => SourceFileMerger.CreateSingleSourceFile(options);
121+
122+
act.Should().Throw<InvalidOperationException>().WithMessage("*SyntheticNotWhitelisted*");
123+
}
124+
125+
private static SourceFileMergeOptions CreateOptions(string targetFile, SourceTargetFramework targetFramework) =>
126+
new ()
127+
{
128+
SourceFolder = TestEnvironment.SourceDirectory.FullName,
129+
TargetFile = targetFile,
130+
TargetFramework = targetFramework,
131+
IncludeVersionComment = false,
132+
};
133+
}

0 commit comments

Comments
 (0)