Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CSharpToJsonSchema.sln
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.UnitTest
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.IntegrationTests", "src\tests\CSharpToJsonSchema.IntegrationTests\CSharpToJsonSchema.IntegrationTests.csproj", "{247C813A-9072-4DF3-B403-B35E3688DB4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpToJsonSchema.AotTests", "src\tests\CSharpToJsonSchema.AotTests\CSharpToJsonSchema.AotTests.csproj", "{6167F915-83EB-42F9-929B-AD4719A55811}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -70,6 +72,10 @@ Global
{247C813A-9072-4DF3-B403-B35E3688DB4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{247C813A-9072-4DF3-B403-B35E3688DB4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{247C813A-9072-4DF3-B403-B35E3688DB4B}.Release|Any CPU.Build.0 = Release|Any CPU
{6167F915-83EB-42F9-929B-AD4719A55811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6167F915-83EB-42F9-929B-AD4719A55811}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6167F915-83EB-42F9-929B-AD4719A55811}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6167F915-83EB-42F9-929B-AD4719A55811}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -82,6 +88,7 @@ Global
{8BDEB07E-C8C8-436C-B736-F3C093CB27AD} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{8AFCC30C-C59D-498D-BE68-9A328B3E5599} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{247C813A-9072-4DF3-B403-B35E3688DB4B} = {AAA11B78-2764-4520-A97E-46AA7089A588}
{6167F915-83EB-42F9-929B-AD4719A55811} = {AAA11B78-2764-4520-A97E-46AA7089A588}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CED9A020-DBA5-4BE6-8096-75E528648EC1}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
Expand All @@ -7,10 +7,11 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoWarn>$(NoWarn);CA1014;CA1031;CA1308</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup Label="Global Usings">
<Using Include="System.Net.Http"/>
<Using Include="System.Net.Http" />
</ItemGroup>

<ItemGroup>
Expand All @@ -22,8 +23,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all"/>
<PackageReference Include="H.Generators.Extensions" Version="1.24.2" PrivateAssets="all"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" />
<PackageReference Include="H.Generators.Extensions" Version="1.24.2" PrivateAssets="all" />

</ItemGroup>

</Project>
130 changes: 130 additions & 0 deletions src/libs/CSharpToJsonSchema.Generators/Conversion/SymbolGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using CSharpToJsonSchema.Generators.JsonGen.Helpers;
using CSharpToJsonSchema.Generators.Models;
using H.Generators.Extensions;

namespace CSharpToJsonSchema.Generators.Conversion;

using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

public static class SymbolGenerator
{
public static ITypeSymbol? GenerateParameterBasedClassSymbol(
string rootNamespace,
IMethodSymbol methodSymbol,
Compilation originalCompilation)
{


// Example: we create a class name
var className = $"{methodSymbol.Name}Args";

// Build property declarations based on method parameters
var properties = methodSymbol
.Parameters
.Where(s=>s.Type.Name != "CancellationToken")
.Select(p =>
{

var propertyTypeName = p.Type.ToDisplayString(); // or another approach
var decleration = SyntaxFactory.PropertyDeclaration(
SyntaxFactory.ParseTypeName(propertyTypeName),
p.Name.ToPropertyName())
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.WithAccessorList(
SyntaxFactory.AccessorList(
SyntaxFactory.List(new[]
{
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
})
)
);
if (p.Type.TypeKind == TypeKind.Enum)
{
//decleration = decleration.AddAttributeLists(AttributeList(SingletonSeparatedList(GetConverter(propertyTypeName))));
}
return decleration;
})
.ToArray();

// Build a class declaration
var classDecl = SyntaxFactory.ClassDeclaration(className)
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddMembers(properties);

// We create a compilation unit holding our new class



var namespaceName =rootNamespace; // choose your own
var ns = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName(namespaceName))
.AddMembers(classDecl);

var compilationUnit = SyntaxFactory.CompilationUnit()
.AddMembers(ns) // if ns is a NamespaceDeclarationSyntax
.NormalizeWhitespace();

var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(originalCompilation.GetLanguageVersion()?? LanguageVersion.Default);
var syntaxTree =CSharpSyntaxTree.Create(compilationUnit,parseOptions);
//CSharpSyntaxTree.Create(ns.NormalizeWhitespace());

// Now we need to add this syntax tree to a new or existing compilation
var assemblyName = "TemporaryAssembly";
var compilation = originalCompilation
.AddSyntaxTrees(syntaxTree);
//.WithAssemblyName(assemblyName);


// Get the semantic model for our newly added syntax tree
var semanticModel = compilation.GetSemanticModel(syntaxTree);

// Find the class syntax node in the syntax tree
var classNode = syntaxTree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.FirstOrDefault();

if (classNode == null) return null;

// Retrieve the ITypeSymbol from the semantic model
var classSymbol = semanticModel.GetDeclaredSymbol(classNode) as ITypeSymbol;
return classSymbol;
}

public static AttributeSyntax GetConverter(string propertyType)
{
// 1. Create the attribute: [JsonConverter(typeof(StringEnumJsonConverter<T>))]
var converterAttribute =
Attribute(
IdentifierName("System.Text.Json.Serialization.JsonConverterAttribute")
)
.WithArgumentList(
AttributeArgumentList(
SingletonSeparatedList(
AttributeArgument(
TypeOfExpression(
GenericName(
Identifier("System.Text.Json.Serialization.JsonStringEnumConverter")
)
.WithTypeArgumentList(
TypeArgumentList(
SingletonSeparatedList<TypeSyntax>(
IdentifierName(propertyType)
)
)
)
)
)
)
)
);
return converterAttribute;

}
}
4 changes: 2 additions & 2 deletions src/libs/CSharpToJsonSchema.Generators/Conversion/ToModels.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.ComponentModel;
using H.Generators;
using CSharpToJsonSchema.Generators.Models;
using Microsoft.CodeAnalysis;

namespace CSharpToJsonSchema.Generators.Core.Conversion;
namespace CSharpToJsonSchema.Generators.Conversion;

public static class ToModels
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.CodeAnalysis;

namespace CSharpToJsonSchema.Generators.JsonGen.Base;

internal abstract class AbstractSyntaxHelper : ISyntaxHelper
{
public abstract bool IsCaseSensitive { get; }

public abstract bool IsValidIdentifier(string name);

public abstract SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode name);

public abstract bool IsAnyNamespaceBlock(SyntaxNode node);

public abstract bool IsAttribute(SyntaxNode node);
public abstract SyntaxNode GetNameOfAttribute(SyntaxNode node);

public abstract bool IsAttributeList(SyntaxNode node);
public abstract SeparatedSyntaxList<SyntaxNode> GetAttributesOfAttributeList(SyntaxNode node);
public abstract void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder<SyntaxNode> targets);

public abstract bool IsLambdaExpression(SyntaxNode node);

public abstract void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global);
public abstract void AddAliases(CompilationOptions options, ref ValueListBuilder<(string aliasName, string symbolName)> aliases);

public abstract bool ContainsGlobalAliases(SyntaxNode root);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.Diagnostics;
using CSharpToJsonSchema.Generators.JsonGen.Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace CSharpToJsonSchema.Generators.JsonGen.Base;

internal sealed class CSharpSyntaxHelper : AbstractSyntaxHelper
{
public static readonly ISyntaxHelper Instance = new CSharpSyntaxHelper();

private CSharpSyntaxHelper()
{
}

public override bool IsCaseSensitive
=> true;

public override bool IsValidIdentifier(string name)
=> SyntaxFacts.IsValidIdentifier(name);

public override bool IsAnyNamespaceBlock(SyntaxNode node)
=> node is BaseNamespaceDeclarationSyntax;

public override bool IsAttribute(SyntaxNode node)
=> node is AttributeSyntax;

public override SyntaxNode GetNameOfAttribute(SyntaxNode node)
=> ((AttributeSyntax)node).Name;

public override bool IsAttributeList(SyntaxNode node)
=> node is AttributeListSyntax;

public override void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder<SyntaxNode> targets)
{
var attributeList = (AttributeListSyntax)node;
var container = attributeList.Parent;
Debug.Assert(container != null);

// For fields/events, the attribute applies to all the variables declared.
if (container is FieldDeclarationSyntax field)
{
foreach (var variable in field.Declaration.Variables)
targets.Append(variable);
}
else if (container is EventFieldDeclarationSyntax ev)
{
foreach (var variable in ev.Declaration.Variables)
targets.Append(variable);
}
else
{
targets.Append(container);
}
}

public override SeparatedSyntaxList<SyntaxNode> GetAttributesOfAttributeList(SyntaxNode node)
=> ((AttributeListSyntax)node).Attributes;

public override bool IsLambdaExpression(SyntaxNode node)
=> node is LambdaExpressionSyntax;

public override SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node)
=> RoslynExtensions2.GetUnqualifiedName(((NameSyntax)node).GetUnqualifiedName()).Identifier;

public override void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global)
{
if (node is CompilationUnitSyntax compilationUnit)
{
AddAliases(compilationUnit.Usings, ref aliases, global);
}
else if (node is BaseNamespaceDeclarationSyntax namespaceDeclaration)
{
AddAliases(namespaceDeclaration.Usings, ref aliases, global);
}
else
{
Debug.Fail("This should not be reachable. Caller already checked we had a compilation unit or namespace.");
}
}

private static void AddAliases(SyntaxList<UsingDirectiveSyntax> usings, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global)
{
foreach (var usingDirective in usings)
{
if (usingDirective.Alias is null)
continue;

if (global != usingDirective.GlobalKeyword.Kind() is SyntaxKind.GlobalKeyword)
continue;

// We only care about aliases from one name to another name. e.g. `using X = A.B.C;` That's because
// the caller is only interested in finding a fully-qualified-metadata-name to an attribute.
if (usingDirective.Name is null)
continue;

var aliasName = usingDirective.Alias.Name.Identifier.ValueText;
var symbolName = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText;
aliases.Append((aliasName, symbolName));
}
}

public override void AddAliases(CompilationOptions compilation, ref ValueListBuilder<(string aliasName, string symbolName)> aliases)
{
// C# doesn't have global aliases at the compilation level.
return;
}

public override bool ContainsGlobalAliases(SyntaxNode root)
{
// Global usings can only exist at the compilation-unit level, so no need to dive any deeper than that.
var compilationUnit = (CompilationUnitSyntax)root;

foreach (var directive in compilationUnit.Usings)
{
if (directive.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword) &&
directive.Alias != null)
{
return true;
}
}

return false;
}
}
Loading
Loading