Skip to content

Commit e804224

Browse files
committed
Require RootNamespace and add C# 10 support with global usings.
The generator will not exclude global usings in the generated source as well as automatically adding the root namespace setting to the global usings as well as the namespace for the GitInformation attribute.
1 parent 55b2106 commit e804224

8 files changed

+181
-31
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,17 @@ Source Generator for dumping the git branch information, commit hash, and if the
77
| Package | Version |
88
|:-------:|:-------:|
99
| GitBuildInfo.SourceGenerator | [![NuGet Badge](https://buildstats.info/nuget/GitBuildInfo.SourceGenerator?includePreReleases=true)](https://www.nuget.org/packages/GitBuildInfo.SourceGenerator/) |
10+
11+
## Usage
12+
13+
1. Install this package into your project with:
14+
```xml
15+
<PackageReference Include="GitBuildInfo.SourceGenerator" IsImplicitlyDefined="true" Version="*-*">
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
```
19+
2. Set the following msbuild properties in your project (or if you have all project file property settings stored in a project specific ``Directory.Build.props``) file:
20+
* ``<GitBuildInfoIsGeneric></GitBuildInfoIsGeneric>`` (Optional, default is false)
21+
* ``<GitBuildInfoAssemblyType></GitBuildInfoAssemblyType>`` (Required, Note: Do not include anything before the type name like a fully qualified namespace or any ``.``'s. For that you need to set ``RootNamespace`` below)
22+
* ``<RootNamespace></RootNamespace>`` (Required unless you want the type to be assumed to be in the ``Elskom.Generic.Libs`` namespace by the generator)
23+
3. The generator package should now run a build task to grab information prior to it executing the generator.

build/GitBuildInfo.SourceGenerator.props

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8+
<!--
9+
Add the namespace for the generated attribute to the global usings list only if the C# language version is 10.0 or newer.
10+
-->
11+
<Using Include="Elskom.Generic.Libs" Condition="'$(ImplicitUsings)' == 'enable'" />
12+
<!--
13+
Add the root namespace to the global usings list only if the C# language version is 10.0 or newer.
14+
-->
15+
<Using Include="$(RootNamespace)" Condition="'$(ImplicitUsings)' == 'enable'" />
16+
<CompilerVisibleProperty Include="RootNamespace" />
817
<CompilerVisibleProperty Include="GitBuildInfoAssemblyType" />
918
<CompilerVisibleProperty Include="GitBuildInfoIsGeneric" />
1019
<CompilerVisibleProperty Include="GitHead" />

src/GitBuildInfo.SourceGenerator/Directory.Build.props

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
<Version>1.0.13</Version>
77
<PackageReleaseNotes>Fixed issue where building can result in a build performance decrease due to having the build task being inline.
88
Also made build task target .NET Standard 2.0 only.
9-
I think this one should work for everyone even for those using .NET Framework's msbuild.</PackageReleaseNotes>
9+
I think this one should work for everyone even for those using .NET Framework's msbuild.
10+
Also the generator now requires setting the RootNamespace msbuild property that holds the type they specifiy in the GitBuildInfoAssemblyType msbuild property.</PackageReleaseNotes>
1011
<Copyright>Copyright (c) 2021</Copyright>
1112
<!-- Suppresses the warnings about the package not having assemblies in lib/*/.dll.-->
1213
<NoPackageAnalysis>true</NoPackageAnalysis>

src/GitBuildInfo.SourceGenerator/Generator.cs

+40-8
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,23 @@ public static string CreateAndGenerateCode(GeneratorOptions options, GitInfo git
2222
try
2323
{
2424
var generator = Create(options, gitInfo, context);
25-
var splitted = generator.options.AssemblyType.Contains(".")
26-
? generator.options.AssemblyType.Split('.')
27-
: Array.Empty<string>();
28-
var splitted2 = splitted.AsSpan().Slice(0, splitted.Length > 0 ? splitted.Length - 1 : 0);
29-
return generator.GenerateCode(
30-
splitted2.ToArray(),
25+
if (generator.options.IsCSharp10OrGreater)
26+
{
27+
return generator.GenerateCodeCSharp10(generator.options.AssemblyType).ToFullString();
28+
}
29+
30+
return generator.GenerateCodeCSharp9(
31+
string.IsNullOrEmpty(generator.options.RootNamespace) ? Array.Empty<string>() : generator.options.RootNamespace.Split('.'),
3132
"Elskom.Generic.Libs",
32-
splitted.Length > 0 ? splitted[splitted2.Length] : generator.options.AssemblyType).ToFullString();
33+
generator.options.AssemblyType).ToFullString();
3334
}
3435
catch (InvalidOperationException)
3536
{
3637
return string.Empty;
3738
}
3839
}
3940

40-
public CompilationUnitSyntax GenerateCode(string[] usings, string originalnamespace, string typeName)
41+
public CompilationUnitSyntax GenerateCodeCSharp9(string[] usings, string originalnamespace, string typeName)
4142
=> SyntaxFactory.CompilationUnit().WithUsings(
4243
SyntaxFactory.List(
4344
string.Equals(string.Join(".", usings), originalnamespace, StringComparison.Ordinal) ||
@@ -78,6 +79,37 @@ usings.Length is 0
7879
SyntaxKind.CloseBracketToken,
7980
SyntaxFactory.TriviaList(SyntaxFactory.LineFeed)))));
8081

82+
public CompilationUnitSyntax GenerateCodeCSharp10(string typeName)
83+
=> SyntaxFactory.CompilationUnit().WithAttributeLists(
84+
SyntaxFactory.SingletonList(
85+
SyntaxFactory.AttributeList(
86+
SyntaxFactory.SingletonSeparatedList(
87+
SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("GitInformationAttribute"))
88+
.WithArgumentList(
89+
SyntaxFactory.AttributeArgumentList(
90+
SyntaxFactory.SeparatedList<AttributeArgumentSyntax>(
91+
this.MakeAttributeArgumentList(typeName))))))
92+
.WithOpenBracketToken(
93+
SyntaxFactory.Token(
94+
SyntaxFactory.TriviaList(
95+
SyntaxFactory.Comment("// <autogenerated/>"),
96+
SyntaxFactory.LineFeed,
97+
SyntaxFactory.LineFeed),
98+
SyntaxKind.OpenBracketToken,
99+
SyntaxFactory.TriviaList()))
100+
.WithTarget(
101+
SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword))
102+
.WithColonToken(
103+
SyntaxFactory.Token(
104+
SyntaxFactory.TriviaList(),
105+
SyntaxKind.ColonToken,
106+
SyntaxFactory.TriviaList(SyntaxFactory.Space))))
107+
.WithCloseBracketToken(
108+
SyntaxFactory.Token(
109+
SyntaxFactory.TriviaList(),
110+
SyntaxKind.CloseBracketToken,
111+
SyntaxFactory.TriviaList(SyntaxFactory.LineFeed)))));
112+
81113
private static Generator Create(GeneratorOptions options, GitInfo gitInfo, GeneratorExecutionContext context)
82114
{
83115
var generator = new Generator(options, gitInfo);

src/GitBuildInfo.SourceGenerator/GeneratorOptions.cs

+4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ public record GeneratorOptions
1414
DiagnosticSeverity.Warning,
1515
true);
1616

17+
public string RootNamespace { get; init; }
18+
1719
public string AssemblyType { get; init; }
1820

1921
public bool IsGeneric { get; init; }
2022

23+
internal bool IsCSharp10OrGreater { get; init; }
24+
2125
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2226
public void Validate(GeneratorExecutionContext context)
2327
{

src/GitBuildInfo.SourceGenerator/SourceGenerator.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ public void Initialize(GeneratorInitializationContext context)
2121
/// <inheritdoc/>
2222
public void Execute(GeneratorExecutionContext context)
2323
{
24-
if (context.Compilation is not CSharpCompilation)
24+
if (context.Compilation is not CSharpCompilation compilation)
2525
{
2626
return;
2727
}
2828

29+
_ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace);
2930
_ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.GitBuildInfoAssemblyType", out var assemblyType);
3031
_ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.GitBuildInfoIsGeneric", out var isGeneric);
3132
_ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.GitHead", out var gitHead);
@@ -35,7 +36,15 @@ public void Execute(GeneratorExecutionContext context)
3536
"GitAssemblyInfo.g.cs",
3637
SourceText.From(
3738
Generator.CreateAndGenerateCode(
38-
new GeneratorOptions { AssemblyType = assemblyType, IsGeneric = Convert.ToBoolean(isGeneric) },
39+
new GeneratorOptions
40+
{
41+
RootNamespace = rootNamespace,
42+
AssemblyType = assemblyType,
43+
IsGeneric = Convert.ToBoolean(isGeneric),
44+
IsCSharp10OrGreater = compilation.LanguageVersion is LanguageVersion.CSharp10
45+
or LanguageVersion.Latest
46+
or LanguageVersion.Preview,
47+
},
3948
new GitInfo { GitHead = gitHead, CommitHash = commitHash, GitBranch = gitBranch },
4049
context),
4150
Encoding.UTF8));

tests/CSGeneratorTest.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,18 @@ public class CSGeneratorTest : CSharpSourceGeneratorTest<SourceGenerator, XUnitV
1111
{
1212
public List<(string, string)> GlobalOptions { get; } = new();
1313

14+
public LanguageVersion LanguageVersion { get; set; }
15+
1416
protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray<ISourceGenerator> sourceGenerators)
1517
=> CSharpGeneratorDriver.Create(
1618
sourceGenerators,
1719
project.AnalyzerOptions.AdditionalFiles,
18-
(CSharpParseOptions)project.ParseOptions!,
20+
(CSharpParseOptions)CreateParseOptions(),
1921
new OptionsProvider(project.AnalyzerOptions.AnalyzerConfigOptionsProvider, GlobalOptions));
22+
23+
protected override ParseOptions CreateParseOptions()
24+
{
25+
return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);
26+
}
2027
}
2128
}

tests/SourceGeneratorTests.cs

+93-19
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,73 @@
22
{
33
using System.Collections.Generic;
44
using System.Threading.Tasks;
5+
using Microsoft.CodeAnalysis.CSharp;
56
using Microsoft.CodeAnalysis.Testing;
67
using Microsoft.CodeAnalysis.Testing.Verifiers;
78
using Xunit;
89

910
public class SourceGeneratorTests
1011
{
1112
private const string generatedFile = "GitBuildInfo.SourceGenerator/GitBuildInfo.SourceGenerator.SourceGenerator/GitAssemblyInfo.g.cs";
13+
14+
#region C# 10 tests.
15+
[Fact]
16+
public async Task TestGeneratingDefaultNamespaceCSharp10()
17+
{
18+
await TestGeneratingDefaultNamespace(LanguageVersion.CSharp10).ConfigureAwait(false);
19+
}
20+
21+
[Fact]
22+
public async Task TestGeneratingNoNamespaceCSharp10()
23+
{
24+
await TestGeneratingNoNamespace(LanguageVersion.CSharp10).ConfigureAwait(false);
25+
}
26+
27+
[Fact]
28+
public async Task TestGeneratingCustomNamespaceCSharp10()
29+
{
30+
await TestGeneratingCustomNamespace(LanguageVersion.CSharp10).ConfigureAwait(false);
31+
}
32+
33+
[Fact]
34+
public async Task TestGeneratingGenericCSharp10()
35+
{
36+
await TestGeneratingGeneric(LanguageVersion.CSharp10).ConfigureAwait(false);
37+
}
38+
#endregion
39+
40+
#region C# 9 tests.
41+
[Fact]
42+
public async Task TestGeneratingDefaultNamespaceCSharp9()
43+
{
44+
await TestGeneratingDefaultNamespace().ConfigureAwait(false);
45+
}
46+
47+
[Fact]
48+
public async Task TestGeneratingNoNamespaceCSharp9()
49+
{
50+
await TestGeneratingNoNamespace().ConfigureAwait(false);
51+
}
1252

1353
[Fact]
14-
public async Task TestGeneratingDefaultNamespace()
54+
public async Task TestGeneratingCustomNamespaceCSharp9()
1555
{
16-
await RunTest<CSGeneratorTest>("Elskom.Generic.Libs.Test", "false", @"// <autogenerated/>
56+
await TestGeneratingCustomNamespace().ConfigureAwait(false);
57+
}
58+
59+
[Fact]
60+
public async Task TestGeneratingGenericCSharp9()
61+
{
62+
await TestGeneratingGeneric().ConfigureAwait(false);
63+
}
64+
#endregion
65+
66+
private static async Task TestGeneratingDefaultNamespace(LanguageVersion languageVersion = LanguageVersion.CSharp9)
67+
{
68+
await RunTest<CSGeneratorTest>(string.Empty, "Test", "false", languageVersion == LanguageVersion.CSharp10 ? @"// <autogenerated/>
69+
70+
[assembly: GitInformationAttribute(""fbgtgretgtre"", ""vfdbttregter"", ""vsdfvfdsv"", typeof(Test))]
71+
" : @"// <autogenerated/>
1772
using Elskom.Generic.Libs;
1873
1974
[assembly: GitInformationAttribute(""fbgtgretgtre"", ""vfdbttregter"", ""vsdfvfdsv"", typeof(Test))]
@@ -23,13 +78,15 @@ internal static class Test
2378
internal static object Dummy()
2479
=> null;
2580
}
26-
}", GetDiagnostics()).ConfigureAwait(false);
81+
}", languageVersion, languageVersion == LanguageVersion.CSharp10 ? GetCSharp10Diagnostics("Test", otherEndColumn: 92) : GetDiagnostics()).ConfigureAwait(false);
2782
}
2883

29-
[Fact]
30-
public async Task TestGeneratingNoNamespace()
84+
private async Task TestGeneratingNoNamespace(LanguageVersion languageVersion = LanguageVersion.CSharp9)
3185
{
32-
await RunTest<CSGeneratorTest>("Test", "false", @"// <autogenerated/>
86+
await RunTest<CSGeneratorTest>(string.Empty, "Test", "false", languageVersion == LanguageVersion.CSharp10 ? @"// <autogenerated/>
87+
88+
[assembly: GitInformationAttribute(""fbgtgretgtre"", ""vfdbttregter"", ""vsdfvfdsv"", typeof(Test))]
89+
" : @"// <autogenerated/>
3390
using Elskom.Generic.Libs;
3491
3592
[assembly: GitInformationAttribute(""fbgtgretgtre"", ""vfdbttregter"", ""vsdfvfdsv"", typeof(Test))]
@@ -39,13 +96,15 @@ internal static class Test
3996
internal static object Dummy()
4097
=> null;
4198
}
42-
}", GetDiagnostics()).ConfigureAwait(false);
99+
}", languageVersion, languageVersion == LanguageVersion.CSharp10 ? GetCSharp10Diagnostics("Test", otherEndColumn: 92) : GetDiagnostics()).ConfigureAwait(false);
43100
}
44101

45-
[Fact]
46-
public async Task TestGeneratingCustomNamespace()
102+
private async Task TestGeneratingCustomNamespace(LanguageVersion languageVersion = LanguageVersion.CSharp9)
47103
{
48-
await RunTest<CSGeneratorTest>("TestNamespace.Test", "false", @"// <autogenerated/>
104+
await RunTest<CSGeneratorTest>("TestNamespace", "Test", "false", languageVersion == LanguageVersion.CSharp10 ? @"// <autogenerated/>
105+
106+
[assembly: GitInformationAttribute(""fbgtgretgtre"", ""vfdbttregter"", ""vsdfvfdsv"", typeof(Test))]
107+
": @"// <autogenerated/>
49108
using Elskom.Generic.Libs;
50109
using TestNamespace;
51110
@@ -56,13 +115,15 @@ internal static class Test
56115
internal static object Dummy()
57116
=> null;
58117
}
59-
}", GetGenericDiagnostics()).ConfigureAwait(false);
118+
}", languageVersion, languageVersion == LanguageVersion.CSharp10 ? GetCSharp10Diagnostics("Test", otherEndColumn: 92) : GetGenericDiagnostics()).ConfigureAwait(false);
60119
}
61120

62-
[Fact]
63-
public async Task TestGeneratingGeneric()
121+
private async Task TestGeneratingGeneric(LanguageVersion languageVersion = LanguageVersion.CSharp9)
64122
{
65-
await RunTest<CSGeneratorTest>("Test", "true", @"// <autogenerated/>
123+
await RunTest<CSGeneratorTest>(string.Empty, "Test", "true", languageVersion == LanguageVersion.CSharp10 ? @"// <autogenerated/>
124+
125+
[assembly: GitInformationAttribute(""fbgtgretgtre"", ""vfdbttregter"", ""vsdfvfdsv"", typeof(Test<>))]
126+
" : @"// <autogenerated/>
66127
using Elskom.Generic.Libs;
67128
68129
[assembly: GitInformationAttribute(""fbgtgretgtre"", ""vfdbttregter"", ""vsdfvfdsv"", typeof(Test<>))]
@@ -72,18 +133,19 @@ internal static class Test<T>
72133
internal static object Dummy()
73134
=> null;
74135
}
75-
}", GetDiagnostics()).ConfigureAwait(false);
136+
}", languageVersion, languageVersion == LanguageVersion.CSharp10 ? GetCSharp10Diagnostics("Test<>") : GetDiagnostics()).ConfigureAwait(false);
76137
}
77138

78139
[Fact]
79140
public async Task TestGeneratingFailure()
80141
{
81142
await RunTest<CSGeneratorTest>(
143+
string.Empty,
82144
string.Empty,
83145
"false",
84146
string.Empty,
85147
string.Empty,
86-
new List<DiagnosticResult>
148+
expectedDiagnostics: new List<DiagnosticResult>
87149
{
88150
new DiagnosticResult(GeneratorOptions.ValidationWarning).WithArguments("AssemblyType"),
89151
}).ConfigureAwait(false);
@@ -93,6 +155,7 @@ await RunTest<CSGeneratorTest>(
93155
public async Task TestVBGeneratingAbort()
94156
{
95157
await RunTest<VBGeneratorTest>(
158+
string.Empty,
96159
string.Empty,
97160
"false",
98161
string.Empty,
@@ -107,6 +170,15 @@ private static List<DiagnosticResult> GetDiagnostics(int startLine = 4, int star
107170
DiagnosticResult.CompilerError("CS0246").WithSpan(generatedFile, startLine, startColumn, endLine, endColumn).WithArguments("GitInformationAttributeAttribute"),
108171
};
109172

173+
// these diagnostics must be ignored because the Elskom.GitInformation project was moved.
174+
private static List<DiagnosticResult> GetCSharp10Diagnostics(string argument, int startLine = 3, int startColumn = 12, int endLine = 3, int endColumn = 35, int otherEndColumn = 94)
175+
{
176+
var expected = new List<DiagnosticResult>();
177+
expected.AddRange(GetDiagnostics(startLine, startColumn, endLine, endColumn));
178+
expected.Add(DiagnosticResult.CompilerError("CS0246").WithSpan(generatedFile, startLine, 88, endLine, otherEndColumn).WithArguments(argument));
179+
return expected;
180+
}
181+
110182
// these diagnostics must be ignored because the Elskom.GitInformation project was moved.
111183
private static List<DiagnosticResult> GetGenericDiagnostics()
112184
{
@@ -119,14 +191,15 @@ private static List<DiagnosticResult> GetGenericDiagnostics()
119191
return result;
120192
}
121193

122-
private static async Task RunTest<TestType>(string assemblyType, string isGeneric, string generatedSource,
123-
string testSource, List<DiagnosticResult>? expectedDiagnostics = null)
194+
private static async Task RunTest<TestType>(string rootNs, string assemblyType, string isGeneric, string generatedSource,
195+
string testSource, LanguageVersion languageVersion = LanguageVersion.CSharp9, List<DiagnosticResult>? expectedDiagnostics = null)
124196
where TestType : SourceGeneratorTest<XUnitVerifier>, IGeneratorTestBase, new()
125197
{
126198
var test = new TestType
127199
{
128200
GlobalOptions =
129201
{
202+
("build_property.RootNamespace", rootNs),
130203
("build_property.GitBuildInfoAssemblyType", assemblyType),
131204
("build_property.GitBuildInfoIsGeneric", isGeneric),
132205
("build_property.GitHead", "fbgtgretgtre"),
@@ -147,8 +220,9 @@ private static async Task RunTest<TestType>(string assemblyType, string isGeneri
147220
test.ExpectedDiagnostics.AddRange(expectedDiagnostics);
148221
}
149222

150-
if (test is not VBGeneratorTest)
223+
if (test is CSGeneratorTest tst)
151224
{
225+
tst.LanguageVersion = languageVersion;
152226
test.TestState.GeneratedSources.Add(
153227
(typeof(SourceGenerator), "GitAssemblyInfo.g.cs", generatedSource));
154228
}

0 commit comments

Comments
 (0)