Skip to content

Commit 9e35119

Browse files
committed
Add C# Source Generator
1 parent 9bf8f69 commit 9e35119

17 files changed

+886
-2
lines changed

Directory.Packages.props

+3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
1515
<PackageVersion Include="NodaTime" Version="3.1.9" />
1616
<PackageVersion Include="NSwag.Core.Yaml" Version="14.0.0-preview011" />
17+
<PackageVersion Include="Parlot" Version="0.0.25.0" />
1718
<PackageVersion Include="Pro.NBench.xUnit" Version="2.0.0" />
1819
<PackageVersion Include="System.ComponentModel.Annotations" Version="4.4.0" />
1920
<PackageVersion Include="System.Text.Json" Version="4.7.2" />
21+
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
22+
<PackageVersion Include="Microsoft.Extensions.FileProviders.Abstractions" Version="8.0.0" />
2023
<PackageVersion Include="Verify.XUnit" Version="14.7.0" />
2124
<PackageVersion Include="xunit" Version="2.8.0" />
2225
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0" />

src/NJsonSchema.Demo/NJsonSchema.Demo.csproj

+14-1
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>
@@ -9,14 +10,26 @@
910
<StartupObject />
1011
<Nullable>disable</Nullable>
1112
</PropertyGroup>
12-
13+
1314
<ItemGroup>
1415
<ProjectReference Include="..\NJsonSchema.Benchmark\NJsonSchema.Benchmark.csproj" />
1516
<ProjectReference Include="..\NJsonSchema\NJsonSchema.csproj" />
17+
<ProjectReference Include="..\NJsonSchema.SourceGenerators.CSharp\NJsonSchema.SourceGenerators.CSharp.csproj"
18+
ReferenceOutputAssembly="false"
19+
OutputItemType="Analyzer" />
1620
</ItemGroup>
1721

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

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

src/NJsonSchema.Demo/schema.json

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

0 commit comments

Comments
 (0)