diff --git a/Directory.Packages.props b/Directory.Packages.props index 94d8c431d..531bffd25 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,9 +14,12 @@ + + + diff --git a/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj b/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj index 7ee582559..2313820dd 100644 --- a/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj +++ b/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj @@ -1,4 +1,5 @@  + net8.0 @@ -9,14 +10,26 @@ disable - + + + + + + diff --git a/src/NJsonSchema.Demo/schema.json b/src/NJsonSchema.Demo/schema.json new file mode 100644 index 000000000..c0634b07f --- /dev/null +++ b/src/NJsonSchema.Demo/schema.json @@ -0,0 +1,21 @@ +{ + "$id": "https://example.com/person.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "lastName": { + "type": "string", + "description": "The person's last name." + }, + "age": { + "description": "Age in years which must be equal to or greater than zero.", + "type": "integer", + "optional": true + } + } +} diff --git a/src/NJsonSchema.SourceGenerators.CSharp.Tests/JsonSchemaSourceGeneratorTests.cs b/src/NJsonSchema.SourceGenerators.CSharp.Tests/JsonSchemaSourceGeneratorTests.cs new file mode 100644 index 000000000..fe29e843b --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp.Tests/JsonSchemaSourceGeneratorTests.cs @@ -0,0 +1,269 @@ +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; +using Xunit.Abstractions; +using static NJsonSchema.SourceGenerators.CSharp.GeneratorConfigurationKeys; + +namespace NJsonSchema.SourceGenerators.CSharp.Tests +{ + public class JsonSchemaSourceGeneratorTests : TestsBase + { + public JsonSchemaSourceGeneratorTests(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void When_no_additional_files_specified_then_no_source_is_generated() + { + var (compilation, outputDiagnostics) = GetGeneratedOutput(null, []); + + Assert.Empty(outputDiagnostics); + Assert.Empty(compilation.SyntaxTrees); + } + + [Fact] + public void When_invalid_path_specified_then_nothing_is_generated() + { + var (compilation, outputDiagnostics) = GetGeneratedOutput(null, [new AdditionalTextStub("not_existing.json")]); + + Assert.NotEmpty(outputDiagnostics); + Assert.Single(outputDiagnostics); + var outputDiagnostic = outputDiagnostics[0]; + Assert.Equal("NJSG001", outputDiagnostic.Id); + Assert.Equal(DiagnosticSeverity.Error, outputDiagnostic.Severity); + + Assert.Empty(compilation.SyntaxTrees); + } + + [Fact] + public void When_without_config_then_generated_with_default_values() + { + var firstName = "Alex"; + var defaultNamespace = "MyNamespace"; + + string source = $@" +namespace Example +{{ + class Test + {{ + public static string RunTest() + {{ + var json = new {defaultNamespace}.Person() + {{ + FirstName = ""{firstName}"" + }}; + return json.FirstName; + }} + }} +}}"; + var (compilation, outputDiagnostics) = GetGeneratedOutput(source, [new AdditionalTextStub("References/schema.json")]); + + Assert.Empty(outputDiagnostics); + + Assert.Equal(2, compilation.SyntaxTrees.Count()); + + Assert.Equal(firstName, RunTest(compilation)); + } + + [Theory] + [InlineData(null, false)] + [InlineData("false", false)] + [InlineData("False", false)] + [InlineData("true", true)] + [InlineData("True", true)] + public void When_GenerateOptionalPropertiesAsNullable_in_global_options_then_generate_according_to_config( + string generateOptionalPropertiesAsNullable, + bool shouldBeNullable) + { + string source = $@" +namespace Example +{{ + class Test + {{ + public static string RunTest() + {{ + var json = new MyNamespace.Person(); + return System.Convert.ToString(json.Age); + }} + }} +}}"; + var globalOptions = new Dictionary + { + { GenerateOptionalPropertiesAsNullable, generateOptionalPropertiesAsNullable } + }; + var (compilation, outputDiagnostics) = GetGeneratedOutput( + source, + [new AdditionalTextStub("References/schema.json")], + globalOptions); + + Assert.Empty(outputDiagnostics); + + Assert.Equal(2, compilation.SyntaxTrees.Count()); + + var expectedOutput = shouldBeNullable ? string.Empty : "0"; + Assert.Equal(expectedOutput, RunTest(compilation)); + } + + [Theory] + [InlineData(null, "true", true)] + [InlineData("false", "true", false)] + [InlineData("False", "true", false)] + [InlineData("true", "false", true)] + [InlineData("True", "false", true)] + public void When_GenerateOptionalPropertiesAsNullable_in_additional_files_then_generate_according_to_config_and_override_global_if_possible( + string generateOptionalPropertiesAsNullableAdditionalFiles, + string generateOptionalPropertiesAsNullableGlobalOptions, + bool shouldBeNullable) + { + string source = $@" +namespace Example +{{ + class Test + {{ + public static string RunTest() + {{ + var json = new MyNamespace.Person(); + return System.Convert.ToString(json.Age); + }} + }} +}}"; + var globalOptions = new Dictionary + { + { GenerateOptionalPropertiesAsNullable, generateOptionalPropertiesAsNullableGlobalOptions } + }; + var additionalFilesOptions = new Dictionary + { + { GenerateOptionalPropertiesAsNullable, generateOptionalPropertiesAsNullableAdditionalFiles } + }; + var (compilation, outputDiagnostics) = GetGeneratedOutput( + source, + [new AdditionalTextStub("References/schema.json", additionalFilesOptions)], + globalOptions); + + Assert.Empty(outputDiagnostics); + + Assert.Equal(2, compilation.SyntaxTrees.Count()); + + var expectedOutput = shouldBeNullable ? string.Empty : "0"; + Assert.Equal(expectedOutput, RunTest(compilation)); + } + + [Theory] + [InlineData(null, null, "MyNamespace")] + [InlineData("", null, "MyNamespace")] + [InlineData(null, "", "MyNamespace")] + [InlineData(null, "NamespaceFromGlobalOptions", "NamespaceFromGlobalOptions")] + [InlineData("NamespaceFromLocalOptions", null, "NamespaceFromLocalOptions")] + [InlineData("NamespaceFromLocalOptions", "NamespaceFromGlobalOptions", "NamespaceFromLocalOptions")] + public void When_Namespace_in_config_then_generate( + string namespaceAdditionalFiles, + string namespaceGlobalOptions, + string expectedNamespace) + { + string source = $@" +namespace Example +{{ + class Test + {{ + public static string RunTest() + {{ + var json = new {expectedNamespace}.Person(); + return ""compiled""; + }} + }} +}}"; + var globalOptions = new Dictionary + { + { Namespace, namespaceGlobalOptions } + }; + var additionalFilesOptions = new Dictionary + { + { Namespace, namespaceAdditionalFiles } + }; + var (compilation, outputDiagnostics) = GetGeneratedOutput( + source, + [new AdditionalTextStub("References/schema.json", additionalFilesOptions)], + globalOptions); + + Assert.Empty(outputDiagnostics); + + Assert.Equal(2, compilation.SyntaxTrees.Count()); + + Assert.Equal("compiled", RunTest(compilation)); + } + + [Theory] + [InlineData(null, null, "Person")] + [InlineData(null, "", "Person")] + [InlineData("", null, "Person")] + [InlineData(null, "ShouldNotOverride", "Person")] + [InlineData("ShouldOverride", null, "ShouldOverride")] + public void When_TypeNameHint_in_config_then_generate_using_additional_files_only( + string typeNameHintAdditionalFiles, + string typeNameHintGlobalOptions, + string expectedTypeName) + { + string source = $@" +namespace Example +{{ + class Test + {{ + public static string RunTest() + {{ + var json = new MyNamespace.{expectedTypeName}(); + return ""compiled""; + }} + }} +}}"; + var globalOptions = new Dictionary + { + { TypeNameHint, typeNameHintGlobalOptions } + }; + var additionalFilesOptions = new Dictionary + { + { TypeNameHint, typeNameHintAdditionalFiles } + }; + var (compilation, outputDiagnostics) = GetGeneratedOutput( + source, + [new AdditionalTextStub("References/schema.json", additionalFilesOptions)], + globalOptions); + + Assert.Empty(outputDiagnostics); + + Assert.Equal(2, compilation.SyntaxTrees.Count()); + + Assert.Equal("compiled", RunTest(compilation)); + } + + [Theory] + [InlineData(null, null, "NJsonSchemaGenerated.g.cs")] + [InlineData("", null, "NJsonSchemaGenerated.g.cs")] + [InlineData(null, "", "NJsonSchemaGenerated.g.cs")] + [InlineData(null, "ShouldNotOverride.g.cs", "NJsonSchemaGenerated.g.cs")] + [InlineData("ShouldOverride.g.cs", null, "ShouldOverride.g.cs")] + public void When_FileName_in_config_then_generate_using_additional_files_only( + string fileNameAdditionalFiles, + string fileNameGlobalOptions, + string expectedFileName) + { + var globalOptions = new Dictionary + { + { FileName, fileNameGlobalOptions } + }; + var additionalFilesOptions = new Dictionary + { + { FileName, fileNameAdditionalFiles } + }; + var (compilation, outputDiagnostics) = GetGeneratedOutput( + null, + [new AdditionalTextStub("References/schema.json", additionalFilesOptions)], + globalOptions); + + Assert.Empty(outputDiagnostics); + + Assert.Single(compilation.SyntaxTrees); + var syntaxTree = compilation.SyntaxTrees.First(); + Assert.EndsWith(expectedFileName, syntaxTree.FilePath); + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.SourceGenerators.CSharp.Tests/NJsonSchema.SourceGenerators.CSharp.Tests.csproj b/src/NJsonSchema.SourceGenerators.CSharp.Tests/NJsonSchema.SourceGenerators.CSharp.Tests.csproj new file mode 100644 index 000000000..72a3dbf91 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp.Tests/NJsonSchema.SourceGenerators.CSharp.Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + false + false + disable + true + false + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NJsonSchema.SourceGenerators.CSharp.Tests/TestsBase.cs b/src/NJsonSchema.SourceGenerators.CSharp.Tests/TestsBase.cs new file mode 100644 index 000000000..540332002 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp.Tests/TestsBase.cs @@ -0,0 +1,240 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using Xunit.Abstractions; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace NJsonSchema.SourceGenerators.CSharp.Tests +{ + /// + /// Helps with AdditionalFiles by exposing them as text files. + /// + public class AdditionalTextStub : AdditionalText + { + private readonly string _path; + + public AdditionalTextStub(string path, Dictionary options = null) + { + _path = path; + Options = options; + } + + public Dictionary Options { get; } + + public override string Path + { + get + { + return _path; + } + } + + public override SourceText GetText(CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + } + + public abstract class TestsBase + { + protected readonly ITestOutputHelper _output; + private static List _metadataReferences; + private static readonly object Lock = new object(); + + protected TestsBase(ITestOutputHelper output) + { + _output = output; + } + + /// + /// Retrieves and caches referenced assemblies, so that tested compilations can make use of them. + /// + private static List MetadataReferences + { + get + { + lock (Lock) + { + if (_metadataReferences == null) + { + _metadataReferences = new List(); + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + if (!assembly.IsDynamic) + { + _metadataReferences.Add(MetadataReference.CreateFromFile(assembly.Location)); + } + } + } + } + + return _metadataReferences; + } + } + + /// + /// Takes compiled input and runs the code. + /// + /// Compiled code + /// If specified, this list will be populated with diagnostics that can be used for debugging + /// + protected string RunTest(Compilation compilation, List diagnostics = null) + { + if (compilation == null) + { + throw new ArgumentException($"Argument {nameof(compilation)} must not be null"); + } + + // Get the compilation and load the assembly + using var memoryStream = new MemoryStream(); + EmitResult result = compilation.Emit(memoryStream); + + if (result.Success) + { + memoryStream.Seek(0, SeekOrigin.Begin); + Assembly assembly = Assembly.Load(memoryStream.ToArray()); + + // We assume the generated code has a type Example.Test that contains a method RunTest(Async), to run the test + Type testClassType = assembly.GetType("Example.Test"); + var method = testClassType?.GetMethod("RunTest") ?? testClassType?.GetMethod("RunTestAsync"); + if (method == null) + { + return "-- could not find test method --"; + } + + // Actually invoke the method and return the result + var resultObj = method.Invoke(null, Array.Empty()); + if (resultObj is not string stringResult) + { + return "-- result was not a string --"; + } + + // Log the test output, for debugging purposes + _output.WriteLine($"Generated test output:\r\n===\r\n{stringResult}\r\n===\r\n"); + + return stringResult; + } + + // If diagnostics list is specified, fill it with any diagnostics. If not, fail the unit test directly. + if (diagnostics == null) + { + Assert.Fail( + $"Compilation did not succeed:\r\n{string.Join("\r\n", result.Diagnostics.Select(d => $"{Enum.GetName(typeof(DiagnosticSeverity), d.Severity)} ({d.Location}) - {d.GetMessage()}"))}"); + } + else + { + diagnostics.AddRange(result.Diagnostics); + } + + return null; + } + + /// + /// Build a compilation and run the source generator. + /// + /// Input source + /// Additional files that must be added to the compilation + /// Optional; if specified, this will be filled with info for debugging + /// + protected (Compilation, IImmutableList) GetGeneratedOutput( + string source, + IEnumerable additionalTexts, + Dictionary globalOptions = null) + { + List syntaxTrees = null; + if (source != null) + { + syntaxTrees = [CSharpSyntaxTree.ParseText(source)]; + } + + var references = MetadataReferences; + + var compilation = CSharpCompilation.Create( + "TestImplementation", + syntaxTrees, + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var generator = new JsonSchemaSourceGenerator(); + + var analyzerConfigOptionsProvider = new TestAnalyzerConfigOptionsProvider(additionalTexts, globalOptions); + + CSharpGeneratorDriver.Create( + [generator.AsSourceGenerator()], + additionalTexts.Cast(), + new CSharpParseOptions(), + analyzerConfigOptionsProvider) + .RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics); + + var output = outputCompilation + .SyntaxTrees + .Where(t => t.GetText().ToString().Contains("")) + .ToArray(); + + if (output.Length > 0) + { + _output.WriteLine($"Generated code:\r\n===\r\n{string.Join("\r\n===\r\n", output.AsEnumerable())}\r\n===\r\n"); + } + + return (outputCompilation, generateDiagnostics); + } + } + + public class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider + { + private readonly IEnumerable _additionalTexts; + + public TestAnalyzerConfigOptionsProvider( + IEnumerable additionalTexts, + Dictionary globalOptions = null) + { + GlobalOptions = new GlobalTestAnalyzerConfigOptions(globalOptions); + _additionalTexts = additionalTexts; + } + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => new AdditionalFilesTestAnalyzerConfigOptions(); + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + { + return new AdditionalFilesTestAnalyzerConfigOptions(_additionalTexts.FirstOrDefault(x => x.Path == textFile.Path).Options); + } + + public override AnalyzerConfigOptions GlobalOptions { get; } + + private abstract class TestAnalyzerConfigOptions : AnalyzerConfigOptions + { + private readonly Dictionary _options; + + protected TestAnalyzerConfigOptions(Dictionary options = null) + { + _options = options ?? []; + } + + public override bool TryGetValue(string key, out string value) => _options.TryGetValue(key, out value); + } + + private class AdditionalFilesTestAnalyzerConfigOptions : TestAnalyzerConfigOptions + { + public AdditionalFilesTestAnalyzerConfigOptions(Dictionary options = null) + : base(options?.Select(x => ("build_metadata.AdditionalFiles." + x.Key, x.Value)).ToDictionary(pair => pair.Item1, p => p.Value)) + { + } + } + + private class GlobalTestAnalyzerConfigOptions : TestAnalyzerConfigOptions + { + public GlobalTestAnalyzerConfigOptions(Dictionary options) + : base(options?.Select(x => ("build_property." + x.Key, x.Value)).ToDictionary(pair => pair.Item1, p => p.Value)) + { + } + } + } +} diff --git a/src/NJsonSchema.SourceGenerators.CSharp/AnalyzerConfigOptionsProviderExtensions.cs b/src/NJsonSchema.SourceGenerators.CSharp/AnalyzerConfigOptionsProviderExtensions.cs new file mode 100644 index 000000000..da13f9462 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/AnalyzerConfigOptionsProviderExtensions.cs @@ -0,0 +1,71 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static NJsonSchema.SourceGenerators.CSharp.GeneratorConfigurationKeys; + +namespace NJsonSchema.SourceGenerators.CSharp +{ + /// + /// Extension methods for . + /// + public static class AnalyzerConfigOptionsProviderExtensions + { + /// + /// Converts options from to . + /// + /// Analyzer options provider. + /// Additional text item. + /// + public static JsonSchemaSourceGeneratorConfig ToJsonSchemaSourceGeneratorConfig( + this AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, + AdditionalText additionalText) + { + var additionTextOptions = analyzerConfigOptionsProvider.GetOptions(additionalText); + var globalOptions = analyzerConfigOptionsProvider.GlobalOptions; + + return new JsonSchemaSourceGeneratorConfig( + GetOptionBoolean(GenerateOptionalPropertiesAsNullable, globalOptions, additionTextOptions), + GetOption(Namespace, globalOptions, additionTextOptions), + GetAdditionalFileOption(TypeNameHint, additionTextOptions), + GetAdditionalFileOption(FileName, additionTextOptions)); + } + + private static bool? GetOptionBoolean( + string key, + AnalyzerConfigOptions globalOptions, + AnalyzerConfigOptions additionTextOptions) + { + var option = GetOption(key, globalOptions, additionTextOptions); + + if (bool.TryParse(option, out bool result)) + { + return result; + } + + return null; + } + + private static string? GetOption( + string key, + AnalyzerConfigOptions globalOptions, + AnalyzerConfigOptions additionTextOptions) + { + return GetAdditionalFileOption(key, additionTextOptions) ?? GetGlobalOption(key, globalOptions); + } + + private static string? GetGlobalOption( + string key, + AnalyzerConfigOptions globalOptions) + { + globalOptions.TryGetValue($"build_property.{key}", out var value); + return value; + } + + private static string? GetAdditionalFileOption( + string key, + AnalyzerConfigOptions additionTextOptions) + { + additionTextOptions.TryGetValue($"build_metadata.AdditionalFiles.{key}", out var value); + return value; + } + } +} diff --git a/src/NJsonSchema.SourceGenerators.CSharp/GeneratorConfigurationKeys.cs b/src/NJsonSchema.SourceGenerators.CSharp/GeneratorConfigurationKeys.cs new file mode 100644 index 000000000..75fdb32dc --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/GeneratorConfigurationKeys.cs @@ -0,0 +1,30 @@ +namespace NJsonSchema.SourceGenerators.CSharp +{ + /// + /// Contains contstants with configuration keys used for Source Generation. + /// + public static class GeneratorConfigurationKeys + { + private const string _prefix = "NJsonSchema_"; + + /// + /// Indicating whether optional schema properties (not required) are generated as nullable properties. + /// + public const string GenerateOptionalPropertiesAsNullable = _prefix + "GenerateOptionalPropertiesAsNullable"; + + /// + /// .NET namespace of the generated types. + /// + public const string Namespace = _prefix + "Namespace"; + + /// + /// C# class name. + /// + public const string TypeNameHint = _prefix + "TypeNameHint"; + + /// + /// Name of the file containing generated classes. + /// + public const string FileName = _prefix + "FileName"; + } +} diff --git a/src/NJsonSchema.SourceGenerators.CSharp/JsonSchemaSourceGenerator.cs b/src/NJsonSchema.SourceGenerators.CSharp/JsonSchemaSourceGenerator.cs new file mode 100644 index 000000000..faa601c1a --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/JsonSchemaSourceGenerator.cs @@ -0,0 +1,64 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using NJsonSchema.CodeGeneration.CSharp; +using System; +using System.Text; + +namespace NJsonSchema.SourceGenerators.CSharp +{ + /// + /// Generates C# classes from JSON schema + /// + [Generator] + public class JsonSchemaSourceGenerator : IIncrementalGenerator + { + /// + /// Configure source generation. + /// + /// Source generator context. + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var emitLoggingPipeline = context.AdditionalTextsProvider + .Combine(context.AnalyzerConfigOptionsProvider) + .Select((pair, cancellationToken) => (pair.Left.Path, Config: pair.Right.ToJsonSchemaSourceGeneratorConfig(pair.Left))); + + context.RegisterSourceOutput(emitLoggingPipeline, + (ctx, pair) => + { + + (string path, JsonSchemaSourceGeneratorConfig config) = pair; + var settings = new CSharpGeneratorSettings(); + + if (config.GenerateOptionalPropertiesAsNullable.HasValue) + { + settings.GenerateOptionalPropertiesAsNullable = config.GenerateOptionalPropertiesAsNullable.Value; + } + + if (!string.IsNullOrEmpty(config.Namespace)) + { + settings.Namespace = config.Namespace!; + } + + try + { + var schema = JsonSchema.FromFileAsync(path).GetAwaiter().GetResult(); + var generator = new CSharpGenerator(schema, settings); + var classesFileContent = string.IsNullOrEmpty(config.TypeNameHint) + ? generator.GenerateFile() + : generator.GenerateFile(config.TypeNameHint!); + + var fileName = string.IsNullOrEmpty(config.FileName) ? "NJsonSchemaGenerated.g.cs" : config.FileName; + ctx.AddSource(fileName!, SourceText.From(classesFileContent, Encoding.UTF8)); + } + catch (Exception ex) + { + ctx.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor( + "NJSG001", "Source Generator error", ex.ToString(), "SourceGenerator", DiagnosticSeverity.Error, true), + Location.None)); + } + }); + } + } +} diff --git a/src/NJsonSchema.SourceGenerators.CSharp/JsonSchemaSourceGeneratorConfig.cs b/src/NJsonSchema.SourceGenerators.CSharp/JsonSchemaSourceGeneratorConfig.cs new file mode 100644 index 000000000..91dabcf96 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/JsonSchemaSourceGeneratorConfig.cs @@ -0,0 +1,17 @@ +namespace NJsonSchema.SourceGenerators.CSharp +{ + /// + /// Configuration for source generator. + /// + /// Value indicating whether optional schema properties (not required) are generated as nullable properties (default: false). + /// .NET namespace of the generated types (default: MyNamespace). + /// C# class name. + /// Name of the file containing generated classes. + public record JsonSchemaSourceGeneratorConfig( + bool? GenerateOptionalPropertiesAsNullable, + string? Namespace, + string? TypeNameHint, + string? FileName) + { + } +} diff --git a/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.csproj b/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.csproj new file mode 100644 index 000000000..044c4cdf6 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.csproj @@ -0,0 +1,74 @@ + + + + + netstandard2.0 + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + latest + true + false + true + 1701;1702;NU5128 + + + + + TargetFramework=netstandard2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + + + + + + + + + + + diff --git a/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.nuspec b/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.nuspec new file mode 100644 index 000000000..8a3b520a4 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.nuspec @@ -0,0 +1,12 @@ + + + + $id$ + $version$ + $author$ + $description$ + json schema validation source generator codegen .net + http://NJsonSchema.org + https://github.com/RicoSuter/NJsonSchema/blob/master/LICENSE.md + + \ No newline at end of file diff --git a/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.props b/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.props new file mode 100644 index 000000000..1882bdd09 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/NJsonSchema.SourceGenerators.CSharp.props @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/NJsonSchema.SourceGenerators.CSharp/NuGetIcon.png b/src/NJsonSchema.SourceGenerators.CSharp/NuGetIcon.png new file mode 100644 index 000000000..bd2ae5416 Binary files /dev/null and b/src/NJsonSchema.SourceGenerators.CSharp/NuGetIcon.png differ diff --git a/src/NJsonSchema.SourceGenerators.CSharp/Properties/launchSettings.json b/src/NJsonSchema.SourceGenerators.CSharp/Properties/launchSettings.json new file mode 100644 index 000000000..4f0376f63 --- /dev/null +++ b/src/NJsonSchema.SourceGenerators.CSharp/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "DebugGenerator": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\NJsonSchema.Demo\\NJsonSchema.Demo.csproj" + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.sln b/src/NJsonSchema.sln index 0f04d31e5..050df6724 100644 --- a/src/NJsonSchema.sln +++ b/src/NJsonSchema.sln @@ -32,6 +32,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00 Build", "00 Build", "{863B2D88-A0BD-4466-8583-AAD0B8D3F182}" ProjectSection(SolutionItems) = preProject ..\Directory.Build.props = ..\Directory.Build.props + ..\Directory.Packages.props = ..\Directory.Packages.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.csproj", "{8E4E5A64-B5B7-4718-A92F-CB6B08512264}" @@ -46,6 +47,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02 CodeGeneration", "02 Cod EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NJsonSchema.Annotations", "NJsonSchema.Annotations\NJsonSchema.Annotations.csproj", "{526020E1-D3B5-49F6-BE11-5F4402A02E92}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03 SourceGeneration", "03 SourceGeneration", "{6E8895D5-BDA2-4F8B-919E-01EA89D0C012}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NJsonSchema.SourceGenerators.CSharp", "NJsonSchema.SourceGenerators.CSharp\NJsonSchema.SourceGenerators.CSharp.csproj", "{AE9517AE-C469-4ED5-BC46-9EBEEE4332FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NJsonSchema.SourceGenerators.CSharp.Tests", "NJsonSchema.SourceGenerators.CSharp.Tests\NJsonSchema.SourceGenerators.CSharp.Tests.csproj", "{9CF2F754-27FE-4A05-974A-A9EED7D8ECC4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -118,6 +125,14 @@ Global {526020E1-D3B5-49F6-BE11-5F4402A02E92}.Debug|Any CPU.Build.0 = Debug|Any CPU {526020E1-D3B5-49F6-BE11-5F4402A02E92}.Release|Any CPU.ActiveCfg = Release|Any CPU {526020E1-D3B5-49F6-BE11-5F4402A02E92}.Release|Any CPU.Build.0 = Release|Any CPU + {AE9517AE-C469-4ED5-BC46-9EBEEE4332FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE9517AE-C469-4ED5-BC46-9EBEEE4332FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE9517AE-C469-4ED5-BC46-9EBEEE4332FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE9517AE-C469-4ED5-BC46-9EBEEE4332FC}.Release|Any CPU.Build.0 = Release|Any CPU + {9CF2F754-27FE-4A05-974A-A9EED7D8ECC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CF2F754-27FE-4A05-974A-A9EED7D8ECC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CF2F754-27FE-4A05-974A-A9EED7D8ECC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CF2F754-27FE-4A05-974A-A9EED7D8ECC4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -138,6 +153,8 @@ Global {A9C2A9CD-44F6-4A21-9D72-00CF5BE0A36F} = {CDBC7AC4-9D3D-430D-86BF-CDAECBDD715A} {AFA1E1E7-37F9-4958-B0E3-ADB12F53E563} = {CDBC7AC4-9D3D-430D-86BF-CDAECBDD715A} {526020E1-D3B5-49F6-BE11-5F4402A02E92} = {CDBC7AC4-9D3D-430D-86BF-CDAECBDD715A} + {AE9517AE-C469-4ED5-BC46-9EBEEE4332FC} = {6E8895D5-BDA2-4F8B-919E-01EA89D0C012} + {9CF2F754-27FE-4A05-974A-A9EED7D8ECC4} = {6E8895D5-BDA2-4F8B-919E-01EA89D0C012} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9D5EDC80-5611-493B-804B-8B364816952B} diff --git a/src/NJsonSchema/CompilerFeatures.cs b/src/NJsonSchema/CompilerFeatures.cs index d0b78f8b1..532714dc7 100644 --- a/src/NJsonSchema/CompilerFeatures.cs +++ b/src/NJsonSchema/CompilerFeatures.cs @@ -21,7 +21,7 @@ namespace System.Runtime.CompilerServices /// This class should not be used by developers in source code. /// [EditorBrowsable(EditorBrowsableState.Never)] - public static class IsExternalInit + internal static class IsExternalInit { } }