Skip to content

Commit de1a601

Browse files
committed
Add C# Source Generator
1 parent 55fd77e commit de1a601

17 files changed

+829
-2
lines changed

Directory.Packages.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
1515
<PackageVersion Include="NodaTime" Version="3.2.0" />
1616
<PackageVersion Include="NSwag.Core.Yaml" Version="14.0.0" />
17+
<PackageVersion Include="Parlot" Version="1.1.0" />
1718
<PackageVersion Include="Pro.NBench.xUnit" Version="2.0.0" />
1819
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
20+
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
21+
<PackageVersion Include="Microsoft.Extensions.FileProviders.Abstractions" Version="8.0.0" />
1922
<PackageVersion Include="Verify.XUnit" Version="28.3.2" />
2023
<PackageVersion Include="xunit" Version="2.9.2" />
2124
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />

src/NJsonSchema.Demo/NJsonSchema.Demo.csproj

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2+
<Import Project="../NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.props" />
23

34
<PropertyGroup>
45
<TargetFramework>net8.0</TargetFramework>
@@ -10,14 +11,26 @@
1011
<Nullable>disable</Nullable>
1112
<NoWarn>$(NoWarn);CS1591;IDE0005</NoWarn>
1213
</PropertyGroup>
13-
14+
1415
<ItemGroup>
1516
<ProjectReference Include="..\NJsonSchema.Benchmark\NJsonSchema.Benchmark.csproj" />
1617
<ProjectReference Include="..\NJsonSchema\NJsonSchema.csproj" />
18+
<ProjectReference Include="..\NJsonSchema.SourceGenerators.CSharp\NJsonSchema.SourceGenerators.CSharp.csproj"
19+
ReferenceOutputAssembly="false"
20+
OutputItemType="Analyzer" />
1721
</ItemGroup>
1822

1923
<ItemGroup>
2024
<None Update="Tests\**\*.json" CopyToOutputDirectory="PreserveNewest" />
2125
</ItemGroup>
2226

27+
<ItemGroup>
28+
<AdditionalFiles
29+
Include="schema.json"
30+
NJsonSchema_GenerateOptionalPropertiesAsNullable="true"
31+
NJsonSchema_Namespace="NJsonSchema.Demo.Generated"
32+
NJsonSchema_TypeNameHint="PersonGenerated"
33+
NJsonSchema_FileName="Person.g.cs" />
34+
</ItemGroup>
35+
2336
</Project>

src/NJsonSchema.Demo/schema.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$id": "https://example.com/person.schema.json",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"title": "Person",
5+
"type": "object",
6+
"properties": {
7+
"firstName": {
8+
"type": "string",
9+
"description": "The person's first name."
10+
},
11+
"lastName": {
12+
"type": "string",
13+
"description": "The person's last name."
14+
},
15+
"age": {
16+
"description": "Age in years which must be equal to or greater than zero.",
17+
"type": "integer",
18+
"optional": true
19+
}
20+
}
21+
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
using Microsoft.CodeAnalysis;
2+
using Xunit.Abstractions;
3+
using static NJsonSchema.SourceGenerators.CSharp.GeneratorConfigurationKeys;
4+
5+
namespace NJsonSchema.SourceGenerators.CSharp.Tests
6+
{
7+
public class JsonSchemaSourceGeneratorTests(ITestOutputHelper output) : TestsBase(output)
8+
{
9+
[Fact]
10+
public void When_no_additional_files_specified_then_no_source_is_generated()
11+
{
12+
var (compilation, outputDiagnostics) = GetGeneratedOutput(null, []);
13+
14+
Assert.Empty(outputDiagnostics);
15+
Assert.Empty(compilation.SyntaxTrees);
16+
}
17+
18+
[Fact]
19+
public void When_invalid_path_specified_then_nothing_is_generated()
20+
{
21+
var (compilation, outputDiagnostics) = GetGeneratedOutput(null, [new AdditionalTextStub("not_existing.json")]);
22+
23+
Assert.NotEmpty(outputDiagnostics);
24+
Assert.Single(outputDiagnostics);
25+
var outputDiagnostic = outputDiagnostics[0];
26+
Assert.Equal("NJSG001", outputDiagnostic.Id);
27+
Assert.Equal(DiagnosticSeverity.Error, outputDiagnostic.Severity);
28+
29+
Assert.Empty(compilation.SyntaxTrees);
30+
}
31+
32+
[Fact]
33+
public void When_without_config_then_generated_with_default_values()
34+
{
35+
var firstName = "Alex";
36+
var defaultNamespace = "MyNamespace";
37+
38+
string source = $@"
39+
namespace Example
40+
{{
41+
class Test
42+
{{
43+
public static string RunTest()
44+
{{
45+
var json = new {defaultNamespace}.Person()
46+
{{
47+
FirstName = ""{firstName}""
48+
}};
49+
return json.FirstName;
50+
}}
51+
}}
52+
}}";
53+
var (compilation, outputDiagnostics) = GetGeneratedOutput(source, [new AdditionalTextStub("References/schema.json")]);
54+
55+
Assert.Empty(outputDiagnostics);
56+
57+
Assert.Equal(2, compilation.SyntaxTrees.Count());
58+
59+
Assert.Equal(firstName, RunTest(compilation));
60+
}
61+
62+
[Theory]
63+
[InlineData(null, false)]
64+
[InlineData("false", false)]
65+
[InlineData("False", false)]
66+
[InlineData("true", true)]
67+
[InlineData("True", true)]
68+
public void When_GenerateOptionalPropertiesAsNullable_in_global_options_then_generate_according_to_config(
69+
string generateOptionalPropertiesAsNullable,
70+
bool shouldBeNullable)
71+
{
72+
string source = $@"
73+
namespace Example
74+
{{
75+
class Test
76+
{{
77+
public static string RunTest()
78+
{{
79+
var json = new MyNamespace.Person();
80+
return System.Convert.ToString(json.Age);
81+
}}
82+
}}
83+
}}";
84+
var globalOptions = new Dictionary<string, string>
85+
{
86+
{ GenerateOptionalPropertiesAsNullable, generateOptionalPropertiesAsNullable }
87+
};
88+
var (compilation, outputDiagnostics) = GetGeneratedOutput(
89+
source,
90+
[new AdditionalTextStub("References/schema.json")],
91+
globalOptions);
92+
93+
Assert.Empty(outputDiagnostics);
94+
95+
Assert.Equal(2, compilation.SyntaxTrees.Count());
96+
97+
var expectedOutput = shouldBeNullable ? string.Empty : "0";
98+
Assert.Equal(expectedOutput, RunTest(compilation));
99+
}
100+
101+
[Theory]
102+
[InlineData(null, "true", true)]
103+
[InlineData("false", "true", false)]
104+
[InlineData("False", "true", false)]
105+
[InlineData("true", "false", true)]
106+
[InlineData("True", "false", true)]
107+
public void When_GenerateOptionalPropertiesAsNullable_in_additional_files_then_generate_according_to_config_and_override_global_if_possible(
108+
string generateOptionalPropertiesAsNullableAdditionalFiles,
109+
string generateOptionalPropertiesAsNullableGlobalOptions,
110+
bool shouldBeNullable)
111+
{
112+
string source = $@"
113+
namespace Example
114+
{{
115+
class Test
116+
{{
117+
public static string RunTest()
118+
{{
119+
var json = new MyNamespace.Person();
120+
return System.Convert.ToString(json.Age);
121+
}}
122+
}}
123+
}}";
124+
var globalOptions = new Dictionary<string, string>
125+
{
126+
{ GenerateOptionalPropertiesAsNullable, generateOptionalPropertiesAsNullableGlobalOptions }
127+
};
128+
var additionalFilesOptions = new Dictionary<string, string>
129+
{
130+
{ GenerateOptionalPropertiesAsNullable, generateOptionalPropertiesAsNullableAdditionalFiles }
131+
};
132+
var (compilation, outputDiagnostics) = GetGeneratedOutput(
133+
source,
134+
[new AdditionalTextStub("References/schema.json", additionalFilesOptions)],
135+
globalOptions);
136+
137+
Assert.Empty(outputDiagnostics);
138+
139+
Assert.Equal(2, compilation.SyntaxTrees.Count());
140+
141+
var expectedOutput = shouldBeNullable ? string.Empty : "0";
142+
Assert.Equal(expectedOutput, RunTest(compilation));
143+
}
144+
145+
[Theory]
146+
[InlineData(null, null, "MyNamespace")]
147+
[InlineData("", null, "MyNamespace")]
148+
[InlineData(null, "", "MyNamespace")]
149+
[InlineData(null, "NamespaceFromGlobalOptions", "NamespaceFromGlobalOptions")]
150+
[InlineData("NamespaceFromLocalOptions", null, "NamespaceFromLocalOptions")]
151+
[InlineData("NamespaceFromLocalOptions", "NamespaceFromGlobalOptions", "NamespaceFromLocalOptions")]
152+
public void When_Namespace_in_config_then_generate(
153+
string namespaceAdditionalFiles,
154+
string namespaceGlobalOptions,
155+
string expectedNamespace)
156+
{
157+
string source = $@"
158+
namespace Example
159+
{{
160+
class Test
161+
{{
162+
public static string RunTest()
163+
{{
164+
var json = new {expectedNamespace}.Person();
165+
return ""compiled"";
166+
}}
167+
}}
168+
}}";
169+
var globalOptions = new Dictionary<string, string>
170+
{
171+
{ Namespace, namespaceGlobalOptions }
172+
};
173+
var additionalFilesOptions = new Dictionary<string, string>
174+
{
175+
{ Namespace, namespaceAdditionalFiles }
176+
};
177+
var (compilation, outputDiagnostics) = GetGeneratedOutput(
178+
source,
179+
[new AdditionalTextStub("References/schema.json", additionalFilesOptions)],
180+
globalOptions);
181+
182+
Assert.Empty(outputDiagnostics);
183+
184+
Assert.Equal(2, compilation.SyntaxTrees.Count());
185+
186+
Assert.Equal("compiled", RunTest(compilation));
187+
}
188+
189+
[Theory]
190+
[InlineData(null, null, "Person")]
191+
[InlineData(null, "", "Person")]
192+
[InlineData("", null, "Person")]
193+
[InlineData(null, "ShouldNotOverride", "Person")]
194+
[InlineData("ShouldOverride", null, "ShouldOverride")]
195+
public void When_TypeNameHint_in_config_then_generate_using_additional_files_only(
196+
string typeNameHintAdditionalFiles,
197+
string typeNameHintGlobalOptions,
198+
string expectedTypeName)
199+
{
200+
string source = $@"
201+
namespace Example
202+
{{
203+
class Test
204+
{{
205+
public static string RunTest()
206+
{{
207+
var json = new MyNamespace.{expectedTypeName}();
208+
return ""compiled"";
209+
}}
210+
}}
211+
}}";
212+
var globalOptions = new Dictionary<string, string>
213+
{
214+
{ TypeNameHint, typeNameHintGlobalOptions }
215+
};
216+
var additionalFilesOptions = new Dictionary<string, string>
217+
{
218+
{ TypeNameHint, typeNameHintAdditionalFiles }
219+
};
220+
var (compilation, outputDiagnostics) = GetGeneratedOutput(
221+
source,
222+
[new AdditionalTextStub("References/schema.json", additionalFilesOptions)],
223+
globalOptions);
224+
225+
Assert.Empty(outputDiagnostics);
226+
227+
Assert.Equal(2, compilation.SyntaxTrees.Count());
228+
229+
Assert.Equal("compiled", RunTest(compilation));
230+
}
231+
232+
[Theory]
233+
[InlineData(null, null, "NJsonSchemaGenerated.g.cs")]
234+
[InlineData("", null, "NJsonSchemaGenerated.g.cs")]
235+
[InlineData(null, "", "NJsonSchemaGenerated.g.cs")]
236+
[InlineData(null, "ShouldNotOverride.g.cs", "NJsonSchemaGenerated.g.cs")]
237+
[InlineData("ShouldOverride.g.cs", null, "ShouldOverride.g.cs")]
238+
public void When_FileName_in_config_then_generate_using_additional_files_only(
239+
string fileNameAdditionalFiles,
240+
string fileNameGlobalOptions,
241+
string expectedFileName)
242+
{
243+
var globalOptions = new Dictionary<string, string>
244+
{
245+
{ FileName, fileNameGlobalOptions }
246+
};
247+
var additionalFilesOptions = new Dictionary<string, string>
248+
{
249+
{ FileName, fileNameAdditionalFiles }
250+
};
251+
var (compilation, outputDiagnostics) = GetGeneratedOutput(
252+
null,
253+
[new AdditionalTextStub("References/schema.json", additionalFilesOptions)],
254+
globalOptions);
255+
256+
Assert.Empty(outputDiagnostics);
257+
258+
Assert.Single(compilation.SyntaxTrees);
259+
var syntaxTree = compilation.SyntaxTrees.First();
260+
Assert.EndsWith(expectedFileName, syntaxTree.FilePath);
261+
}
262+
}
263+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
7+
<Nullable>disable</Nullable>
8+
<IsTestProject>true</IsTestProject>
9+
<EnableNETAnalyzers>false</EnableNETAnalyzers>
10+
<NoWarn>$(NoWarn);1591</NoWarn>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<Content Include="..\NJsonSchema.Demo\schema.json" Link="References\schema.json">
15+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
16+
</Content>
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
21+
<PackageReference Include="xunit" />
22+
<PackageReference Include="xunit.runner.visualstudio" />
23+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
24+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\NJsonSchema.SourceGenerators.CSharp\NJsonSchema.SourceGenerators.CSharp.csproj" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<Using Include="Xunit" />
33+
</ItemGroup>
34+
35+
</Project>

0 commit comments

Comments
 (0)