using InnovEnergy.Lib.SrcGen.Trees; using InnovEnergy.Lib.Utils; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Sawmill; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Microsoft.CodeAnalysis.CSharp.SyntaxKind; namespace InnovEnergy.Lib.SrcGen; public static class NestProperties { private static readonly SyntaxToken PublicKeyword = Token(SyntaxKind.PublicKeyword); public static async Task Generate(Project project, Compilation compilation) { var syntaxTrees = await project .Documents .Where(d => d.Project == project) .Where(d => d.SupportsSyntaxTree) .Select(async d => await d.GetSyntaxRootAsync()) .WhenAll(); syntaxTrees.NotNull() .SelectMany(c => c.GetNestPropertiesAttributes()) .ForEach(GenerateFile); String GenerateFile(AttributeSyntax attribute) { var typeName = attribute.ArgumentList!.Arguments.Single().ToString().Replace("\"", ""); var type = attribute.GetAncestor(); var nameSpace = attribute.GetAncestor(); var filePath = type.SyntaxTree.FilePath; var generatedPath = filePath.GetGeneratedPath(nameof(NestProperties)); var recordProperties = type .Members .OfType() .Where(m => m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword))) .ToList(); var propNames = recordProperties.Select(m => m.Identifier.ValueText); var prefixTree = PrefixTree.Create(propNames); prefixTree.WriteLine(); Console.WriteLine("$$$$$$$$$$$$$"); var wrappedTypeName = type.Identifier.ValueText; var records = new List(); prefixTree.Fold, MemberDeclarationSyntax>((memory, tree) => { var path = tree .Ancestors .Prepend(tree) .Aggregate("", (a, b) => b.Node + a); if (tree.IsLeaf) { var prop = recordProperties.First(p => p.Identifier.ValueText == path); var writable = prop .DescendantNodes() .OfType() .Any(d => d.IsKind(SetAccessorDeclaration) && !d.DescendantTokens().Any(t => t.IsKind(PrivateKeyword))); return CreateAccessorProperty ( prop.Type, tree.Node, path, writable ); } else { var children = memory.ToArray(); var rec = CreateWrapperRecord(typeName + path, wrappedTypeName, children); records.Add(rec); return CreateWrapperProperty(IdentifierName(typeName + path), Identifier(tree.Node)); } }); //x.NormalizeWhitespace().GetText().WriteLine(); records.Reverse(); records.ForEach(r => r.NormalizeWhitespace().WriteLine("\n")); Console.WriteLine($"Generating {generatedPath}"); return ""; } } private static CompilationUnitSyntax CreateCompilationUnit(NameSyntax nameSpaceName, params MemberDeclarationSyntax[] members) { var nameSpace = NamespaceDeclaration(nameSpaceName) .WithMembers(members.Apply(List)); return CompilationUnit().WithMembers(nameSpace.Apply(SingletonList)); } private static RecordDeclarationSyntax CreateWrapperRecord(String structName, String wrappedTypeName, IEnumerable properties) { const String self = "Self"; var wrappedType = IdentifierName(wrappedTypeName); var recId = Identifier(structName); var modifiers = TokenList(Token(SyntaxKind.PublicKeyword), Token(ReadOnlyKeyword)); //properties.Prepend(selfProperty).Prepend(constructor).Apply(List); // var structMembers = new MemberDeclarationSyntax[] { selfProperty, constructor } // .Apply(List); var recMembers = properties.Apply(List); var recParams = self .Apply(Identifier) .Apply(Parameter) .WithType(wrappedType) .Apply(SingletonSeparatedList) .Apply(ParameterList); return RecordDeclaration(Token(RecordKeyword), recId) .WithModifiers(modifiers) .WithClassOrStructKeyword(Token(StructKeyword)) .WithParameterList(recParams) .WithModifiers(modifiers) .WithOpenBraceToken(Token(OpenBraceToken)) .WithMembers(recMembers) .WithCloseBraceToken(Token(CloseBraceToken)); //return /*StructDeclaration(structId)*/ } private static PropertyDeclarationSyntax CreateAccessorProperty(TypeSyntax propertyTypeName, String propertyName, String parentPropertyName, Boolean writeable) { const String self = "Self"; const String value = "value"; var propertyModifier = SyntaxKind .PublicKeyword .Apply(Token) .Apply(TokenList); var semicolonToken = Token(SemicolonToken); var accessExpression = MemberAccessExpression ( SimpleMemberAccessExpression, IdentifierName(self), IdentifierName(parentPropertyName) ); var getterBody = ArrowExpressionClause(accessExpression); var getter = GetAccessorDeclaration .Apply(AccessorDeclaration) .WithExpressionBody(getterBody) .WithSemicolonToken(semicolonToken); var setterBody = AssignmentExpression(SimpleAssignmentExpression, accessExpression, IdentifierName(value)) .Apply(ArrowExpressionClause); var setter = SetAccessorDeclaration .Apply(AccessorDeclaration) .WithExpressionBody(setterBody) .WithSemicolonToken(semicolonToken); var accessors = writeable ? new[] { getter, setter } : new[] { getter }; var accessorsList = accessors .Apply(List) .Apply(AccessorList); var property = PropertyDeclaration ( type : propertyTypeName, identifier: Identifier(propertyName) ); return property .WithModifiers(propertyModifier) .WithAccessorList(accessorsList); } private static PropertyDeclarationSyntax CreateWrapperProperty(TypeSyntax type, SyntaxToken identifier) { var modifiers = SyntaxKind .PublicKeyword .Apply(Token) .Apply(TokenList); var self = IdentifierName("Self") .Apply(Argument) .Apply(SingletonSeparatedList) .Apply(ArgumentList); var body = ObjectCreationExpression(type) .WithArgumentList(self) .Apply(ArrowExpressionClause); return PropertyDeclaration(type, identifier) .WithModifiers(modifiers) .WithExpressionBody(body) .WithSemicolonToken(Token(SemicolonToken)); } }