diff --git a/sources/SilkTouch/Clang/ContextCSharpSyntaxRewriter.cs b/sources/SilkTouch/Clang/ContextCSharpSyntaxRewriter.cs new file mode 100644 index 0000000000..5166b1b7a3 --- /dev/null +++ b/sources/SilkTouch/Clang/ContextCSharpSyntaxRewriter.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp; + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// which contains a queryable current context +/// +/// +public abstract class ContextCSharpSyntaxRewriter(bool visitIntoStructuredTrivia = false) + : CSharpSyntaxRewriter(visitIntoStructuredTrivia) +{ + /// + /// The current type being visited + /// + public TypeContainer? CurrentContext { get; internal set; } + + /// + /// The list of the namespaces being used + /// + public List Usings { get; internal set; } = []; + + /// + /// The current Namespace being visited + /// + public INamespaceContext? CurrentNamespaceContext { get; internal set; } + + /// + /// The top-level namespace context (global context) + /// + public INamespaceContext? TopNamespaceContext { get; internal set; } + + /// + /// The namespace of the current context + /// + public string CurrentNamespace => CurrentNamespaceContext?.FullNamespace ?? string.Empty; + + /// + /// The currently SyntaxContext + /// + public SyntaxContext? Context { get; internal set; } + + /// + /// The file that is currently being editted + /// + public string File { get; internal set; } = string.Empty; + + /// + /// Called when a file is started being worked on + /// + /// + public virtual void OnFileStarted(string fileName) { } + + /// + /// Called when a file is finished being worked on + /// + /// + public virtual void OnFileFinished(string fileName) { } + + /// + /// Whether or not this file should be skipped upon visiting + /// + /// + /// + public virtual bool ShouldSkipFile(string fileName) { return false; } +} diff --git a/sources/SilkTouch/Clang/ContextCSharpSyntaxVisitor.cs b/sources/SilkTouch/Clang/ContextCSharpSyntaxVisitor.cs new file mode 100644 index 0000000000..eef5ec0109 --- /dev/null +++ b/sources/SilkTouch/Clang/ContextCSharpSyntaxVisitor.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// A with metadata for the current Visit method +/// +public class ContextCSharpSyntaxVisitor : CSharpSyntaxVisitor +{ + /// + /// The current type being visited + /// + public IBaseTypeContext? CurrentContext { get; internal set; } + + /// + /// The list of the namespaces being used + /// + public List Usings { get; internal set; } = []; + + /// + /// The current Namespace being visited + /// + public INamespaceContext? CurrentNamespaceContext { get; internal set; } + + /// + /// The top-level namespace context (global context) + /// + public INamespaceContext? TopNamespaceContext { get; internal set; } + + /// + /// The namespace of the current context + /// + public string CurrentNamespace => CurrentNamespaceContext?.FullNamespace ?? string.Empty; + + /// + /// The currently SyntaxContext + /// + public SyntaxContext? Context { get; internal set; } + + /// + /// The file that is currently being editted + /// + public string File { get; internal set; } = string.Empty; + + /// + /// Called when a file is started being worked on + /// + /// + public virtual void OnFileStarted(string fileName) { } + + /// + /// Called when a file is finished being worked on + /// + /// + public virtual void OnFileFinished(string fileName) { } + + /// + /// Whether or not this file should be skipped upon visiting + /// + /// + /// + public virtual bool ShouldSkipFile(string fileName) { return false; } +} diff --git a/sources/SilkTouch/Clang/IBaseTypeContext.cs b/sources/SilkTouch/Clang/IBaseTypeContext.cs new file mode 100644 index 0000000000..398932308b --- /dev/null +++ b/sources/SilkTouch/Clang/IBaseTypeContext.cs @@ -0,0 +1,280 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// A representation of a class, struct, etc. in a SyntaxContext +/// +public interface IBaseTypeContext +{ + /// + /// A which represents this type + /// + TypeSyntax Syntax { get; } + + /// + /// The name of the file that contains this type + /// + string FileName { get; } + + /// + /// The name of the type + /// + string Name { get; } + + /// + /// The containing type context + /// + IBaseTypeContext? Parent { get; } + + /// + /// The number of GenericParameters + /// + int GenericParameterCount { get; } + + /// + /// The SyntaxNode representing this type + /// + BaseTypeDeclarationSyntax? Node { get; } + + /// + /// A list of generic parameters + /// + IEnumerable GenericParameters { get; } + + /// + /// Attempts to get Type object that is contained within this type + /// + /// + /// + /// + /// + bool TryGetSubType(string typeName, out TypeContainer type, int genericParameterCount = 0); + + /// + /// Attempts to Add or overwrites a sub type within this type + /// + /// + /// + bool TryAddSubType(BaseTypeDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Attempts to Add or overwrites a sub type within this type + /// + /// + /// + bool TryAddSubType(DelegateDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Removes the subtype with the given name and number of parameters within this type + /// + /// + /// + void RemoveSubType(string name, int genericParameterCount = 0); + + /// + /// Removes all subtypes with the given name within this type + /// + /// + void RemoveSubTypes(string name); + + /// + /// All subtypes contained within this type + /// + IEnumerable<(string, IEnumerable)> SubTypes { get; } + + /// + /// Attempts to get the type and pointer depth of the field in this type + /// + /// + /// + /// + bool TryGetField(string fieldName, out Field field); + + /// + /// Attempts to Add or overwrites a field within this type + /// + /// + /// + bool TryAddField(BaseFieldDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Removes the field with the given name within this type + /// + /// + void RemoveField(string name); + + /// + /// All Fields contained within this tyoe + /// + IEnumerable<(string, Field)> Fields { get; } + + /// + /// Attempts to get the type and pointer depth of the property in this type + /// + /// + /// + /// + bool TryGetProperty(string propertyName, out Property property); + + /// + /// Attempts to Add or overwrites a property with the given name within this type + /// + /// + /// + bool TryAddProperty(BasePropertyDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Removes the property with the given name within this type + /// + /// + void RemoveProperty(string name); + + /// + /// All Properties contained within this type + /// + IEnumerable<(string, Property)> Properties { get; } + + /// + /// Does this type inherit from the given type + /// + /// + /// + bool HasBaseType(string baseType); + + /// + /// Does this type inherit from the given type + /// + /// + /// + bool HasBaseType(BaseTypeSyntax baseType); + + /// + /// Attempts to Add or overwrites a base type for this type to derive from + /// + /// + /// + /// + /// + bool TryAddBaseType(BaseTypeSyntax baseType, SyntaxContext context, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Removes a type from the list of parent types + /// + /// + void RemoveBaseType(string baseType); + + /// + /// All types this type derives from + /// + IEnumerable<(string, BaseTypeSyntax, IBaseTypeContext)> BaseTypes { get; } + + /// + /// Attempts to get all methods with a given name and their info + /// + /// + /// + /// + bool TryGetMethods(string name, out IEnumerable methodInfo); + + /// + /// Attempts to Add or overwrites a method within this type + /// + /// + /// + bool TryAddMethod(MethodDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Removes the method with the given name and matching parameters within this type + /// + /// + /// + void RemoveMethod(string name, params TypeSyntax[] parameters); + + /// + /// Removes the methods with the given name within this type + /// + /// + void RemoveMethods(string name); + + /// + /// All methods contained within this type + /// + IEnumerable<(string, IEnumerable)> Methods { get; } + + /// + /// Rewrites the current type with a given rewriter and some metadata + /// + /// + /// current namespace + /// file this type is in + /// + TypeContainer? Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file); + + /// + /// Visits the current type with a given visitor and some metadata + /// + /// + void Visit(ContextCSharpSyntaxVisitor visitor); + + /// + /// Converts this type to a complete + /// + /// + MemberDeclarationSyntax? ToCompletedNode(); + + /// + /// Changes the parent for this type + /// + /// + void SetParent(IBaseTypeContext? parent); + + /// + /// Sets all subTypes to null to be cleaned later + /// + void Delete(); + + /// + /// Represents a type defined within a type + /// + /// + /// + public record struct SubType(BaseTypeDeclarationSyntax? Node = null, IBaseTypeContext? TypeContext = null); + + /// + /// Represents a field defined within a type + /// + /// + /// + /// + public record struct Field(BaseFieldDeclarationSyntax? Node = null, TypeContainer? Type = null, int TypePointerDepth = 0); + + /// + /// Represents a property defined within a type + /// + /// + /// + /// + public record struct Property(BasePropertyDeclarationSyntax? Node = null, TypeContainer? Type = null, int TypePointerDepth = 0); + + /// + /// Represents a method defined within a type + /// + /// + /// + /// + /// + public record struct Method(MethodDeclarationSyntax? Node = null, TypeContainer? ReturnType = null, int ReturnTypePointerDepth = 0, IEnumerable<(string, MethodParameter)>? Parameters = null); + + /// + /// Represents a parameter of a method defined within a type + /// + /// + /// + /// + public record struct MethodParameter(ParameterSyntax Node, TypeContainer? Type, int TypePointerDepth); +} diff --git a/sources/SilkTouch/Clang/IDelegateContext.cs b/sources/SilkTouch/Clang/IDelegateContext.cs new file mode 100644 index 0000000000..9fcb9b2e17 --- /dev/null +++ b/sources/SilkTouch/Clang/IDelegateContext.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// A representation of a Delegate in a SyntaxContext +/// +public interface IDelegateContext +{ + /// + /// The name of the delegate + /// + string Name { get; } + + /// + /// The syntax node + /// + DelegateDeclarationSyntax? Node { get; } + + /// + /// The containing parent type if this delegate is contained within a type + /// + IBaseTypeContext? Parent { get; } + + /// + /// The number of GenericParameters + /// + int GenericParameterCount { get; } + + /// + /// A representation of each of the parameters + /// + IEnumerable<(string, IBaseTypeContext.MethodParameter)> Parameters { get; } + + /// + /// Rewrites the current type with a given rewriter and some metadata + /// + /// + /// current namespace + /// file this type is in + /// + TypeContainer? Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file); + + /// + /// Visits the current type with a given visitor and some metadata + /// + /// + void Visit(ContextCSharpSyntaxVisitor visitor); +} diff --git a/sources/SilkTouch/Clang/IEnumTypeContext.cs b/sources/SilkTouch/Clang/IEnumTypeContext.cs new file mode 100644 index 0000000000..bc43ac05c0 --- /dev/null +++ b/sources/SilkTouch/Clang/IEnumTypeContext.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// Represents an Enumeration type in a SyntaxContext +/// +public interface IEnumTypeContext +{ + /// + /// A which represents this type + /// + TypeSyntax Syntax { get; } + + /// + /// The name of the file that contains this type + /// + string FileName { get; } + + /// + /// The name of the type + /// + string Name { get; } + + /// + /// The containing type context + /// + IBaseTypeContext? Parent { get; } + + /// + /// The SyntaxNode representing this type + /// + EnumDeclarationSyntax? Node { get; } + + /// + /// Attempts to retrieve information about a enum member + /// + /// + /// + /// + bool TryGetEnumMember(string memberName, out EnumMemberDeclarationSyntax? member); + + /// + /// Attempts to Add or overwrites existing enum member within this type + /// + /// + /// + bool TryAddEnumMember(EnumMemberDeclarationSyntax node); + + /// + /// Removes the enum member with the given name within this type + /// + /// + void RemoveEnumMember(string name); + + /// + /// All enumerable members contained within this type + /// + IEnumerable<(string, EnumMemberDeclarationSyntax)> EnumMembers { get; } + + /// + /// Rewrites the current type with a given rewriter and some metadata + /// + /// + /// current namespace + /// file this type is in + /// + TypeContainer? Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file); + + /// + /// Visits the current type with a given visitor and some metadata + /// + /// + void Visit(ContextCSharpSyntaxVisitor visitor); + + /// + /// Converts this type to a complete + /// + /// + MemberDeclarationSyntax? ToCompletedNode(); + + /// + /// Changes the parent for this type + /// + /// + void SetParent(IBaseTypeContext? parent); + + /// + /// Does this type inherit from the given type + /// + /// + /// + bool HasBaseType(string baseType); + + /// + /// Does this type inherit from the given type + /// + /// + /// + bool HasBaseType(BaseTypeSyntax baseType); + + /// + /// All types this type derives from + /// + IEnumerable<(string, BaseTypeSyntax, IBaseTypeContext)> BaseTypes { get; } +} diff --git a/sources/SilkTouch/Clang/INamespaceContext.cs b/sources/SilkTouch/Clang/INamespaceContext.cs new file mode 100644 index 0000000000..d976f8500f --- /dev/null +++ b/sources/SilkTouch/Clang/INamespaceContext.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Silk.NET.SilkTouch.Clang.IBaseTypeContext; + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// Represents a namespace within a SyntaxContext +/// +public interface INamespaceContext +{ + /// + /// The parent namespace if one exists + /// + INamespaceContext? Parent { get; } + /// + /// The full namespace within this namespace object + /// + string FullNamespace { get; } + /// + /// Name of the current namespace (just the final element) + /// + string Name { get; } + + /// + /// The Syntax node representing this namespace + /// + BaseNamespaceDeclarationSyntax? Node { get; } + + /// + /// Adds the namespace declaration + /// + /// + /// + void AddNamespace(BaseNamespaceDeclarationSyntax syntax, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Removes a namespace with the given name within this namespace + /// + /// + void RemoveNamespace(string name); + + /// + /// Attempt to get a namespace with the given name within this namespace + /// + /// + /// + /// + bool TryGetNamespace(string name, out INamespaceContext? ns); + + /// + /// All the namespaces contained within this namespace + /// + IEnumerable<(string, INamespaceContext)> Namespaces { get; } + + /// + /// Attempts to get Type object that is contained within this namespace + /// + /// + /// + /// + /// + bool TryGetType(string typeName, out TypeContainer type, int genericParameterCount = 0); + + /// + /// Attempts to Add or overwrites a sub type within this namespace + /// + /// + /// + bool TryAddType(BaseTypeDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Attempts to Add or overwrites a sub type within this namespace + /// + /// + /// + bool TryAddType(DelegateDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter); + + /// + /// Removes the subtype with the given name and number of parameters within this namespace + /// + /// + /// + void RemoveType(string name, int genericParameterCount = 0); + + /// + /// Removes all subtypes with the given name within this namespace + /// + /// + void RemoveTypes(string name); + + /// + /// All types contained within this namespace + /// + IEnumerable<(string, IEnumerable)> Types { get; } + + /// + /// Merges the members of the given namespace into this one + /// + /// + /// + void Merge(BaseNamespaceDeclarationSyntax ns, ContextCSharpSyntaxRewriter rewriter) + { + foreach (var mem in ns.Members) + { + if (mem is BaseNamespaceDeclarationSyntax subNs) + { + AddNamespace(subNs, rewriter) + } + else if (mem is BaseTypeDeclarationSyntax ty) + { + TryAddType(ty, rewriter); + } + else if (mem is DelegateDeclarationSyntax del) + { + TryAddType(del, rewriter); + } + } + } + + /// + /// Returns a complete version of this node if possible + /// + /// + BaseNamespaceDeclarationSyntax? ToCompletedNode() => null; +} diff --git a/sources/SilkTouch/Clang/SyntaxContext.cs b/sources/SilkTouch/Clang/SyntaxContext.cs new file mode 100644 index 0000000000..db17ecb347 --- /dev/null +++ b/sources/SilkTouch/Clang/SyntaxContext.cs @@ -0,0 +1,2840 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// A representation of the full SyntaxTree used to better provide bindings +/// +public class SyntaxContext +{ + /// + /// + /// + /// + /// + public SyntaxContext(Dictionary files, IReadOnlyList diagnostics) + { + Diagnostics = diagnostics; + //Build initial per file tree + foreach ((var fName, var node) in files) + { + if (node is CompilationUnitSyntax comp) + { + Files.Add(fName, new CompilationContext(fName, comp, this)); + } + else + { + throw new Exception("CompilationUnitSyntax missing"); + } + } + + MergeCommonTypes(); + } + + /// + /// + /// + /// + public SyntaxContext(GeneratedSyntax syntax) + { + Diagnostics = syntax.Diagnostics; + //Build initial per file tree + foreach ((var fName, var node) in syntax.Files) + { + if (node is CompilationUnitSyntax comp) + { + Files.Add(fName, new CompilationContext(fName, comp, this)); + } + else + { + throw new Exception("CompilationUnitSyntax missing"); + } + } + + MergeCommonTypes(); + } + + /// + /// Syntax Context data for each file + /// + internal Dictionary Files = []; + + Dictionary> TypeDefinitionContainers = []; + + /// + /// Set of output diagnostics from ClangSharp + /// + public IReadOnlyList Diagnostics; + + /// + /// Creates a new based on the current state of this Context + /// + /// + public GeneratedSyntax ToGeneratedSyntax() + { + return new(Files.Select(file => new KeyValuePair(file.Key, file.Value.ToCompletedNode())).ToDictionary(), Diagnostics); + } + + /// + /// Visits each node in this context with the provided visitor + /// + /// + public void Visit(ContextCSharpSyntaxVisitor visitor) + { + visitor.Context = this; + + foreach ((var fName, var context) in Files) + { + if (visitor.ShouldSkipFile(fName)) + continue; + visitor.File = fName; + visitor.OnFileStarted(fName); + context.Visit(visitor); + visitor.OnFileFinished(fName); + } + } + + /// + /// Apply Syntax Rewriter on all objects in this Context + /// + /// + public void Rewrite(ContextCSharpSyntaxRewriter rewriter) + { + rewriter.Context = this; + + List Removals = []; + + foreach ((var fName, var context) in Files) + { + if (rewriter.ShouldSkipFile(fName)) + continue; + + rewriter.File = fName; + + rewriter.OnFileStarted(fName); + + if (!context.Rewrite(rewriter, fName)) + { + Removals.Add(fName); + } + + rewriter.OnFileFinished(fName); + } + + foreach (string rem in Removals) + { + Files.Remove(rem); + } + + MergeCommonTypes(); + } + + /// + /// Renames any file matching the old path regex based on the new path regex + /// + /// + /// + public void RenameFile(string oldPathRegex, string newPathRegex) + { + Files = Files.ToDictionary(kvp => Regex.Replace(kvp.Key, oldPathRegex, newPathRegex), kvp => kvp.Value); + } + + /// + /// Removes any file that matches the pathRegex + /// + /// + public void RemoveFile(string pathRegex) + { + List Removals = Files.Keys.Where(key => Regex.IsMatch(key, pathRegex)).ToList(); + + foreach (string rem in Removals) + { + Files.Remove(rem); + } + } + + /// + /// Adds a new file into the SyntaxContext + /// + /// + /// + public void AddFile(string filePath, CompilationUnitSyntax compUnit) + { + if (Files.ContainsKey(filePath)) + { + Files[filePath] = new CompilationContext(filePath, compUnit, this); + } + else + { + Files.Add(filePath, new CompilationContext(filePath, compUnit, this)); + } + + MergeCommonTypes(); + } + + private void MergeCommonTypes() + { + //Merge common types into single types + foreach (var typeDef in TypeDefinitionContainers) + { + if (typeDef.Value.Count <= 1) + { + continue; + } + + for (int i = 0; i < typeDef.Value.Count; i++) + { + if (typeDef.Value[i].IsNull) + { + typeDef.Value.RemoveAt(i); + i--; + continue; + } + List typesToMerge = []; + TypeContextContainer main = typeDef.Value[i]; + string mainFile = typeDef.Value.Where(t => t.Type?.FileName.Length > 0).First().Type!.FileName; + List files = [mainFile]; + + //find all types that are the same as our current main type + //this includes same generic arguments with the same constraints + //We also keep track of all file paths along the way + for (int j = 1; j < typeDef.Value.Count; j++) + { + if (main.FullNamespace != typeDef.Value[j].FullNamespace || + !TypeVarCheck(main, typeDef.Value[j])) + { + continue; + } + + string file = typeDef.Value[j].Type?.FileName ?? string.Empty; + if (file.Length > 0 && !files.Contains(file)) + { + files.Add(file); + } + + typesToMerge.Add(typeDef.Value[j]); + typeDef.Value.RemoveAt(j); + j--; + } + + if (typesToMerge.Count == 0) + { + continue; + } + + //Merge all types into main + MergeTypeContainers(main, typesToMerge); + + //get new path which is placed at the common root of all the paths + mainFile = Path.Combine(FindCommonRoot(files), Path.GetFileName(mainFile)).Replace(".Manual.cs", ".gen.cs"); + + int fileIndex = 1; + + //check that our new path doesn't already exist (unless it is our first file anyways) + while (Files.ContainsKey(mainFile) && !(files[0] == mainFile)) + { + mainFile = mainFile.Replace($"{(fileIndex > 1 ? fileIndex.ToString() : "")}.gen.cs", $"{fileIndex + 1}.gen.cs"); + fileIndex++; + } + + //if our file isn't the same as our old one add the new path and remove the original + if (files[0] != mainFile) + { + Files.Add(mainFile, Files[files[0]]); + Files.Remove(files[0]); + } + + //remove the first file path to avoid confusion + files.RemoveAt(0); + + NamespaceContext ns = Files[mainFile].GetNamespace(main.FullNamespace); + + //make sure our type actually is in our namespace (can happen if first type in declarations was an UnknownTypeContext which shouldn't happen) + if (!ns.Types.TryGetValue(typeDef.Key, out var list)) + { + list = [main]; + ns.Types.Add(typeDef.Key, list); + } + else if (!list.Contains(main)) + { + for (int j = 0; j < list.Count; j++) + { + if (main.FullNamespace != list[j].FullNamespace || + !TypeVarCheck(main, list[j])) + { + continue; + } + list.RemoveAt(j); + j--; + } + + list.Add(main); + } + + //finally check all files have types still and either delete them or clean them of bad types + foreach (var file in files) + { + if (Files.TryGetValue(file, out var comp) && comp.DefinedTypeCount == 0) + { + Files.Remove(file); + } + else if (comp is not null) + { + comp.Clean(this); + } + } + } + } + } + + private TypeContainer? CreateTypeContextFromNode(BaseTypeDeclarationSyntax type, INamespaceContext? ns, string file, List usings, IBaseTypeContext? parent = null) + { + if (type is EnumDeclarationSyntax e) + { + return new(Enum:new EnumContext(file, e, parent)); + + } + else if (type is TypeDeclarationSyntax t) + { + return new(new TypeContext(ns, file, t, this, usings, parent)); + } + return null; + } + + private string FindCommonRoot(List paths) + { + if (paths == null || paths.Count == 0) + return string.Empty; + + if (paths.Count == 1) + return paths[0]; + + string[][] separatedPaths = paths + .Select(path => path.Split(Path.DirectorySeparatorChar)) + .ToArray(); + + string commonPath = string.Join(Path.DirectorySeparatorChar.ToString(), + separatedPaths.First().TakeWhile((part, index) => + separatedPaths.All(path => path.Length > index && path[index] == part))); + + return commonPath; + } + + private bool TypeVarCheck(TypeContextContainer t1, TypeContextContainer t2) + { + if (t1.Enum is not null || t2.Enum is not null) + { + return true; + } + + if (t1.Delegate is DelegateContext d1 && t2.Delegate is DelegateContext d2 && + (d1.GenericParameterCount != d2.GenericParameterCount)) + { + return false; + } + + if (t1.Type is TypeContext ty1 && t2.Type is TypeContext ty2 && + (ty1.GenericParameterCount != ty2.GenericParameterCount)) + { + return false; + } + + return true; + } + + private bool TypeVarCheck(TypeContextContainer type, TypeSyntax syntax) + { + if (syntax is not GenericNameSyntax generic) + { + if (type.Enum is not null || (type.Type is TypeContext ty && + (ty.GenericParameterCount == 0)) || + (type.Delegate is not null && type.Delegate.GenericParameterCount == 0)) + { + return true; + } + return false; + } + if (type.Type is EnumContext) + { + return false; + } + + return (type.Type is not null && type.Type.GenericParameterCount == generic.TypeArgumentList.Arguments.Count) || + (type.Delegate is not null && type.Delegate.GenericParameterCount == generic.TypeArgumentList.Arguments.Count); + } + + private void MergeTypeContainers(TypeContextContainer main, List typesToMerge) + { + while (main.Type is UnknownTypeContext u) + { + main.Type = typesToMerge[0].Type; + main.Visibility = typesToMerge[0].Visibility; + typesToMerge.RemoveAt(0); + } + + SyntaxKind visibility = main.IsPublic ? SyntaxKind.PublicKeyword : SyntaxKind.PrivateKeyword; + bool isStatic = true; + + if (main.Type is EnumContext e) + { + if (!e.Node.Modifiers.Any(mod => mod.IsKind(SyntaxKind.StaticKeyword))) + { + isStatic = false; + } + + bool emptyBaseType = e.Node.BaseList is null || e.Node.BaseList.Types.Count == 0; + + foreach (var merge in typesToMerge) + { + if (visibility != SyntaxKind.PublicKeyword) + { + if (merge.IsPublic) + { + visibility = SyntaxKind.PublicKeyword; + } + else if (visibility != SyntaxKind.ProtectedKeyword && merge.Visibility == SyntaxKind.ProtectedKeyword) + { + visibility = SyntaxKind.ProtectedKeyword; + } + } + + if (merge.Type is EnumContext en) + { + if (isStatic && !en.Node.Modifiers.Any(mod => mod.IsKind(SyntaxKind.StaticKeyword))) + { + isStatic = false; + } + + foreach (var member in en.Members) + { + if (e.Members.ContainsKey(member.Key)) + { + e.Members[member.Key] = member.Value; + } + e.Members.Add(member.Key, member.Value); + } + + e.Node = e.Node.AddModifiers(en.Node.Modifiers.Where(mod => !mod.IsKind(SyntaxKind.PrivateKeyword) && + !mod.IsKind(SyntaxKind.ProtectedKeyword) && + !mod.IsKind(SyntaxKind.PublicKeyword) && + !e.Node.Modifiers.Any(eMod => mod.IsKind(eMod.Kind()))).ToArray()) + .AddAttributeLists(en.Node.AttributeLists.Select(al => + AttributeList(SeparatedList(al.Attributes.Where(at => !e.Node.AttributeLists + .Any(eAl => eAl.Attributes + .Any(eAt => at.ToString() != eAt.ToString())))))).ToArray()); + + if (emptyBaseType) + { + e.Node = e.Node.WithBaseList(en.Node.BaseList); + emptyBaseType = false; + } + } + else if(merge.Type is not UnknownTypeContext) + { + throw new Exception($"{main.FullNamespace}.{e.Node.Identifier.Text} is defined multiple times as both an enum and another type"); + } + merge.Type = null; + } + } + else if (main.Type is TypeContext t) + { + if (!t.Node.Modifiers.Any(mod => mod.IsKind(SyntaxKind.StaticKeyword))) + { + isStatic = false; + } + + bool hasBaseClass = !IsInterface(t.BaseTypes.Values.First()); + + foreach (var merge in typesToMerge) + { + if (visibility != SyntaxKind.PublicKeyword) + { + if (merge.IsPublic) + { + visibility = SyntaxKind.PublicKeyword; + } + else if (visibility != SyntaxKind.ProtectedKeyword && merge.Visibility == SyntaxKind.ProtectedKeyword) + { + visibility = SyntaxKind.ProtectedKeyword; + } + } + + if (merge.Type is TypeContext ty) + { + if (isStatic && !ty.Node.Modifiers.Any(mod => mod.IsKind(SyntaxKind.StaticKeyword))) + { + isStatic = false; + } + + foreach (var member in ty.Methods) + { + if (!t.Methods.TryGetValue(member.Key, out var list)) + { + t.Methods.Add(member.Key, member.Value); + continue; + } + + foreach (var method in member.Value) + { + for (int i = 0; i < list.Count; i++) + { + var mem = list[i]; + if (list.Any(mem => method == mem)) + { + list[i] = mem; + continue; + } + } + + + list.Add(method); + } + } + + bool first = true; + foreach (var bType in ty.BaseTypes) + { + bool isInterface = IsInterface(bType.Value); + if (t.BaseTypes.ContainsKey(bType.Key) && ((first && !hasBaseClass) || isInterface)) + { + first = false; + continue; + } + + first = false; + if (isInterface) + { + t.BaseTypes.Add(bType.Key, bType.Value); + continue; + } + + t.BaseTypes = new Dictionary { [bType.Key] = bType.Value }.Concat(t.BaseTypes).ToDictionary(); + } + + t.Node = t.Node.WithBaseList(BaseList(SeparatedList(t.BaseTypes.Select(bType => (BaseTypeSyntax)SimpleBaseType(bType.Value.Type!.Syntax))))); + + foreach(var member in ty.Fields) + { + if (t.Fields.ContainsKey(member.Key)) + { + t.Fields[member.Key] = member.Value; + } + t.Fields.Add(member.Key, member.Value); + } + + foreach (var member in ty.Properties) + { + if (t.Properties.ContainsKey(member.Key)) + { + t.Properties[member.Key] = member.Value; + } + t.Properties.Add(member.Key, member.Value); + } + + foreach (var member in ty.SubTypes) + { + if (!t.SubTypes.TryGetValue(member.Key, out var list)) + { + t.SubTypes.Add(member.Key, member.Value); + continue; + } + + for (int i = 0; i < list.Count; i++) + { + List typesToMerge2 = []; + for (int j = 1; j < member.Value.Count; j++) + { + if (list[i].FullNamespace != member.Value[j].FullNamespace || + !TypeVarCheck(list[i], member.Value[j])) + { + continue; + } + typesToMerge2.Add(member.Value[j]); + member.Value.RemoveAt(j); + j--; + } + + if (typesToMerge2.Count == 0) + { + continue; + } + + MergeTypeContainers(list[i], typesToMerge2); + } + } + + + t.Node = t.Node.AddModifiers(ty.Node.Modifiers.Where(mod => !mod.IsKind(SyntaxKind.PrivateKeyword) && + !mod.IsKind(SyntaxKind.ProtectedKeyword) && + !mod.IsKind(SyntaxKind.PublicKeyword) && + !t.Node.Modifiers.Any(tMod => mod.IsKind(tMod.Kind()))).ToArray()) + .AddAttributeLists(ty.Node.AttributeLists.Select(al => + AttributeList(SeparatedList(al.Attributes.Where(at => !t.Node.AttributeLists + .Any(tAl => tAl.Attributes + .Any(tAt => at.ToString() != tAt.ToString())))))).ToArray()); + + } + else if (merge.Type is not UnknownTypeContext) + { + throw new Exception($"{main.FullNamespace}.{t.Node.Identifier.Text} is defined multiple times as both an type and an enum"); + } + merge.Type = null; + } + } + + } + + private bool IsInterface(TypeContextContainer container) + { + if (container.Type is not TypeContext t) + { + return false; + } + + return t.Node.IsKind(SyntaxKind.InterfaceDeclaration); + } + + private TypeContextContainer GetTypeContainer(TypeSyntax syn, string ns, List usings, TypeContext? currentType, out int pDepth, IEnumerable genericParameters, string parentName = "") + { + pDepth = 0; + while (syn is PointerTypeSyntax pointer) + { + pDepth++; + syn = pointer.ElementType; + } + + if (genericParameters.Any(gen => gen.ToString() == syn.ToString())) + { + return new TypeContextContainer(null, new UnknownTypeContext(syn), SyntaxKind.PublicKeyword); + } + + string name = $"{(parentName.Length > 0 ? $"{parentName}." : "")}{currentType?.Node.Identifier.Text ?? string.Empty}"; + + TypeContextContainer? type = null; + List? list; + if (TypeDefinitionContainers.TryGetValue(syn.ToString(), out list)) + { + + foreach (var decl in list) + { + if ((NamespaceMatch(ns, decl.FullNamespace) || (usings.Contains(decl.FullNamespace) && decl.IsPublic)) && TypeVarCheck(decl, syn)) + { + type = decl; + break; + } + } + } + else if (GetChildContainer(syn, currentType, out list, name) && list is not null) + { + type = list.First(); + + foreach (var decl in list) + { + if (ns == decl.FullNamespace && TypeVarCheck(decl, syn)) + { + type = decl; + break; + } + } + } + + if (type is null) + { + type = new(null, new UnknownTypeContext(syn), SyntaxKind.PublicKeyword); + if (!TypeDefinitionContainers.TryGetValue(syn.ToString(), out list)) + { + list = []; + TypeDefinitionContainers.Add(syn.ToString(), list); + } + list.Add(type); + } + + return type; + } + + private void AddTypeContextContainer(TypeContextContainer container) + { + if (!TypeDefinitionContainers.TryGetValue(container.Type!.Name, out var list)) + { + list = []; + TypeDefinitionContainers.Add(container.Type!.Name, list); + } + + for (int i = 0; i < list.Count; i++) + { + if (list[i].Type is UnknownTypeContext unknown && + ((unknown.Syntax is not GenericNameSyntax && container.Type!.GenericParameterCount == 0) || + (unknown.Syntax is GenericNameSyntax generic && generic.TypeArgumentList.Arguments.Count == container.Type!.GenericParameterCount))) + { + list[i].Type = container.Type; + list[i].Visibility = container.Visibility; + list[i].Namespace = container.Namespace; + container = list[i]; + return; + } + } + + list.Add(container); + } + + private bool GetChildContainer(TypeSyntax syn, TypeContext? currentType, out List? list, string parentName, bool topLevel = true) + { + list = null; + if (currentType is null) + { + return false; + } + + foreach(var child in currentType.SubTypes) + { + string name = $"{(parentName.Length > 0 ? $"{parentName}." : "")}{child.Key}"; + if (TypeDefinitionContainers.TryGetValue(name, out list)) + { + return true; + } + + foreach (var ch in child.Value) + { + if (GetChildContainer(syn, ch.Type as TypeContext, out list, name, false)) + { + return true; + } + } + } + return false; + } + + private void RenameType(TypeContextContainer container, string oldName) + { + if (TypeDefinitionContainers.TryGetValue(oldName, out var list)) + { + list.RemoveAt(list.IndexOf(container)); + + if (list.Count == 0) + { + TypeDefinitionContainers.Remove(oldName); + } + } + + AddTypeContextContainer(container); + } + + internal class CompilationContext : INamespaceContext + { + public CompilationContext(string file, CompilationUnitSyntax node, SyntaxContext context) + { + List usings = node.Usings.Select(u => u.Name!.ToString()).ToList(); + foreach (var member in node.Members) + { + if (member is BaseNamespaceDeclarationSyntax ns) + { + var nsContext = new NamespaceContext(string.Empty, ns, context, usings, this, file); + Namespaces.Add(nsContext.Node.Name.ToString(), nsContext); + } + else + { + throw new Exception($"CompilationUnit for {file} contains a member of type ({member.GetType()}) which isn't supported"); + } + } + Node = node.WithMembers(List(Array.Empty())); + } + + public CompilationUnitSyntax Node; + public Dictionary Namespaces = []; + public bool IsWalkingNamespaces = false; + List TempNewNamespaces = []; + + public CompilationUnitSyntax ToCompletedNode() + { + return Node.WithMembers(List(Namespaces.Select(n => (MemberDeclarationSyntax)n.Value.ToCompletedNode()))); + } + + public NamespaceContext GetNamespace(string ns) + { + int firstIndex = ns.IndexOf('.'); + if (firstIndex == -1) + { + firstIndex = ns.Length - 1; + } + + string name = ns.Substring(0, firstIndex); + if (!Namespaces.TryGetValue(name, out NamespaceContext? nameSpace)) + { + nameSpace = new NamespaceContext(NamespaceDeclaration(IdentifierName(name))); + Namespaces.Add(name, nameSpace); + } + return nameSpace.GetNamespace(ns.Substring(firstIndex + 1)); + } + + public int DefinedTypeCount => Namespaces.Select(n => n.Value.DefinedTypeCount).Aggregate((a, b) => a + b); + + public INamespaceContext? Parent => null; + + public string FullNamespace => string.Empty; + + public string Name => string.Empty; + + BaseNamespaceDeclarationSyntax? INamespaceContext.Node => null; + + IEnumerable<(string, INamespaceContext)> INamespaceContext.Namespaces => Namespaces.Select(kvp => (kvp.Key, (INamespaceContext)kvp.Value)); + + public IEnumerable<(string, IEnumerable)> Types => Array.Empty<(string, IEnumerable)>(); + + public void Clean(SyntaxContext context) + { + List usings = Node.Usings.Select(u => u.Name!.ToString()).ToList(); + List removals = []; + + foreach (var ns in Namespaces) + { + if (ns.Value.DefinedTypeCount == 0) + { + removals.Add(ns.Key); + } + else + { + if (ns.Value is NamespaceContext nsc) + { + nsc.Parent = this; + } + ns.Value.Clean("", context, usings); + } + } + + foreach (var rem in removals) + { + Namespaces.Remove(rem); + } + } + + public void Visit(ContextCSharpSyntaxVisitor visitor) + { + List usings = Node.Usings.Select(u => u.Name!.ToString()).ToList(); + visitor.Usings = usings; + visitor.TopNamespaceContext = this; + + foreach (var nameSp in Namespaces) + { + nameSp.Value.Visit(visitor, usings); + } + + visitor.Visit(Node); + } + + public bool Rewrite(ContextCSharpSyntaxRewriter rewriter, string file) + { + List usings = Node.Usings.Select(u => u.Name!.ToString()).ToList(); + rewriter.Usings = usings; + rewriter.TopNamespaceContext = this; + + List removals = []; + IsWalkingNamespaces = true; + foreach (var nameSp in Namespaces) + { + var output = nameSp.Value.Rewrite(rewriter, "", file); + if (output.Item1 is null) + { + removals.Add(nameSp.Key); + } + + if (output.Item2 is not null) + { + throw new Exception("Type Declarations not allowed in the global namespace"); + } + } + IsWalkingNamespaces = false; + + foreach (string rem in removals) + { + Namespaces.Remove(rem); + } + + Namespaces = Namespaces.ToDictionary(kvp => kvp.Value.Node.Name.ToString(), kvp => kvp.Value); + + ReconcileNamespaces(rewriter); + + var node = rewriter.Visit(Node) as CompilationUnitSyntax; + + if (node is null) + { + return false; + } + + usings = Node.Usings.Select(u => u.Name!.ToString()).ToList(); + foreach (var member in node.Members) + { + if (member is BaseNamespaceDeclarationSyntax ns) + { + var nsContext = new NamespaceContext(string.Empty, ns, rewriter.Context!, rewriter.Usings, this, file); + Namespaces.Add(nsContext.Node.Name.ToString(), nsContext); + } + else + { + throw new Exception($"CompilationUnit for {file} contains a member of type ({member.GetType()}) which isn't supported"); + } + } + + Node = node.WithMembers(List(Array.Empty())); + + return Namespaces.Count > 0; + } + + public void AddNamespace(BaseNamespaceDeclarationSyntax syntax, ContextCSharpSyntaxRewriter rewriter) + { + if (IsWalkingNamespaces) + { + TempNewNamespaces.Add(syntax); + } + + string name = syntax.Name.ToString(); + if (Namespaces.ContainsKey(name)) + { + var oldNS = Namespaces[name]; + Namespaces[name] = new(rewriter.CurrentNamespace, syntax, rewriter.Context!, rewriter.Usings, this, rewriter.File); + + Namespaces[name].Merge(oldNS); + } + else + { + Namespaces.Add(name, new(rewriter.CurrentNamespace, syntax, rewriter.Context!, rewriter.Usings, this, rewriter.File)); + } + } + + public void ReconcileNamespaces(ContextCSharpSyntaxRewriter rewriter) + { + foreach(var syntax in TempNewNamespaces) + { + AddNamespace(syntax, rewriter); + } + TempNewNamespaces.Clear(); + } + + public void RemoveNamespace(string name) + { + Namespaces.Remove(name); + } + + public bool TryGetNamespace(string name, out INamespaceContext? ns) + { + var ret = Namespaces.TryGetValue(name, out var nameSp); + ns = nameSp; + return ret; + } + + public bool TryGetType(string typeName, out TypeContainer type, int genericParameterCount = 0) + { + type = new(); + return false; + } + public bool TryAddType(BaseTypeDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) => false; + public bool TryAddType(DelegateDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) => false; + public void RemoveType(string name, int genericParameterCount = 0) { } + public void RemoveTypes(string name) { } + } + + internal class NamespaceContext : INamespaceContext + { + public NamespaceContext(string namesp, BaseNamespaceDeclarationSyntax node, SyntaxContext context, List usings, INamespaceContext? parent, string file = "") + { + Parent = parent; + string[] names = node.Name.ToString().Split('.'); + + namesp += $"{(namesp.Length > 0 ? "." : "")}{node.Name}"; + if (names.Length != 1) + { + NamespaceContext parentNs = new NamespaceContext(namesp, node.WithName(IdentifierName(names[1])), context, usings, this, file); + namesp = namesp.Remove(namesp.Length - (names.Last().Length + 1)); + node = node.WithMembers(List(Array.Empty())); + Namespaces.Add(names[1], parentNs); + + for (int i = 2; i >= names.Length; i++) + { + NamespaceContext current = new NamespaceContext(namesp, node.WithName(IdentifierName(names[i])), context, usings, parentNs, file); + namesp = namesp.Remove(namesp.Length - (names[i].Length + 1)); + parentNs.Namespaces.Add(names[i + 1], current); + parentNs = current; + } + + + Node = node.WithName(IdentifierName(names[0])); + return; + } + + foreach(var member in node.Members) + { + if (member is BaseNamespaceDeclarationSyntax ns) + { + var nsContext = new NamespaceContext(namesp, ns, context, usings, this, file); + Namespaces.Add(nsContext.Node.Name.ToString(), nsContext); + } + else if (member is BaseTypeDeclarationSyntax bT) + { + var ty = new TypeContextContainer(this, context.CreateTypeContextFromNode(bT, this, file, usings), bT.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + + context.AddTypeContextContainer(ty); + + if (!Types.TryGetValue(bT.Identifier.Text, out var list)) + { + list = []; + Types.Add(bT.Identifier.Text, list); + } + list.Add(ty); + } + else + { + throw new Exception($"Namespace {node.Name}{(file != "" ? " in file " + file : "")} contains a member of type ({member.GetType()}) which isn't supported"); + } + } + + Node = node.WithName(IdentifierName(names[0])).WithMembers(List(Array.Empty())); + } + + public NamespaceContext(BaseNamespaceDeclarationSyntax node) + { + Node = node; + } + + public BaseNamespaceDeclarationSyntax Node; + public Dictionary Namespaces = []; + public Dictionary> Types = []; + public INamespaceContext? Parent; + List tempNewNodeList = []; + AddLock _CurrentLock = AddLock.None; + + public BaseNamespaceDeclarationSyntax ToCompletedNode() + { + List members = []; + members.AddRange(Namespaces.Select(n => n.Value.ToCompletedNode())); + foreach (var types in Types) + { + members.AddRange(types.Value.Where(t => t.Type is not null).Select(t => t.Type!.ToCompletedNode()!)); + } + return Node.WithMembers(List(members)); + } + + public NamespaceContext GetNamespace(string ns) + { + if (ns.Length == 0) + { + return this; + } + + int firstIndex = ns.IndexOf('.'); + if (firstIndex == -1) + { + firstIndex = ns.Length - 1; + } + + string name = ns.Substring(0, firstIndex); + if (!Namespaces.TryGetValue(name, out NamespaceContext? nameSpace)) + { + nameSpace = new NamespaceContext(NamespaceDeclaration(IdentifierName(name))); + Namespaces.Add(name, nameSpace); + } + return nameSpace.GetNamespace(ns.Substring(firstIndex + 1)); + } + + public void Visit(ContextCSharpSyntaxVisitor visitor, List usings, string ns = "") + { + visitor.CurrentNamespaceContext = this; + + foreach (var name in Namespaces) + { + name.Value.Visit(visitor, usings, ns); + visitor.CurrentNamespaceContext = this; + } + + foreach (var types in Types) + { + foreach (var type in types.Value) + { + type.Type?.Visit(visitor); + } + } + + visitor.Visit(Node); + } + + + public (BaseNamespaceDeclarationSyntax?, TypeContextContainer?) Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file) + { + //Rewrite members first + string names = $"{(ns.Length > 0 ? $"{ns}." : "")}{Node.Name}"; + + List removals = []; + _CurrentLock = AddLock.Namespace; + foreach (var name in Namespaces) + { + var output = name.Value.Rewrite(rewriter, names, file); + if (output.Item1 is null) + { + removals.Add(name.Key); + } + + if (output.Item2 is not null) + { + + if (!Types.TryGetValue(output.Item2.Type!.Name, out var list)) + { + list = []; + Types.Add(output.Item2.Type!.Name, list); + } + + list.Add(output.Item2); + } + } + + foreach (var rem in removals) + { + Namespaces.Remove(rem); + } + + _CurrentLock = AddLock.Type; + ReconcileNamespaces(rewriter); + + Namespaces = Namespaces.ToDictionary(kvp => kvp.Value.Node.Name.ToString(), kvp => kvp.Value); + + removals.Clear(); + + + Dictionary> newTypes = []; + foreach (var types in Types) + { + for (var i = 0; i < types.Value.Count; i++) + { + var type = types.Value[i]; + if (type.Type is null) + { + types.Value.RemoveAt(i); + i--; + continue; + } + type.ConvertContainer(type.Type.Rewrite(rewriter, names, file)); + + string name = type.Type!.Name; + if (name != types.Key) + { + if (!Types.TryGetValue(name, out var list) && !newTypes.TryGetValue(name, out list)) + { + list = []; + newTypes.Add(name, list); + } + list.Add(type); + + rewriter.Context!.RenameType(type, types.Key); + } + } + + if (types.Value.Count == 0) + { + removals.Add(types.Key); + } + } + + foreach (var rem in removals) + { + Types.Remove(rem); + } + + foreach (var kvp in newTypes) + { + Types.Add(kvp.Key, kvp.Value); + } + + _CurrentLock = AddLock.None; + ReconcileTypes(rewriter); + + //Rewrite and rectify main node + var node = rewriter.Visit(Node); + + (BaseNamespaceDeclarationSyntax?, TypeContextContainer?) ret; + + if (node is BaseNamespaceDeclarationSyntax nameSp) + { + Node = nameSp; + ret = (Node, null); + + names = $"{(ns.Length > 0 ? $"{ns}." : "")}{Node.Name}"; + foreach (var member in Node.Members) + { + if (member is BaseNamespaceDeclarationSyntax subns) + { + var nsContext = new NamespaceContext(names, subns, rewriter.Context!, rewriter.Usings, this, file); + Namespaces.Add(nsContext.Node.Name.ToString(), nsContext); + } + else if (member is BaseTypeDeclarationSyntax bT) + { + var ty = new TypeContextContainer(this, rewriter.Context!.CreateTypeContextFromNode(bT, this, file, rewriter.Usings), bT.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + + rewriter.Context.AddTypeContextContainer(ty); + + if (!Types.TryGetValue(bT.Identifier.Text, out var list)) + { + list = []; + Types.Add(bT.Identifier.Text, list); + } + list.Add(ty); + } + else + { + throw new Exception($"Namespace {Node.Name}{(file != "" ? " in file " + file : "")} contains a member of type ({member.GetType()}) which isn't supported"); + } + } + + } + else if (node is TypeDeclarationSyntax type) + { + names = ns; + var cont = rewriter.Context!.CreateTypeContextFromNode(type, rewriter.CurrentNamespaceContext, file, rewriter.Usings, null); + + var ty = new TypeContextContainer(rewriter.CurrentNamespaceContext, cont, type.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + + + rewriter.Context!.AddTypeContextContainer(ty); + + Types = Types.ToDictionary(kvp => $"{ty.Name}." + kvp.Key, kvp => { + foreach (var val in kvp.Value) + { + val.SetParent(cont!.Value.Type, rewriter.Context); + } + return kvp.Value; + }); + + ret = (null, ty); + + if (cont?.Type is TypeContext tyCont) + { + foreach (var subTypes in Types) + { + if (tyCont!.SubTypes.TryGetValue(subTypes.Key, out var list)) + { + foreach (var sType in subTypes.Value) + { + if (!list.Any(lType => lType.Type?.GenericParameterCount == sType.Type?.GenericParameterCount)) + { + list.Add(sType); + } + } + } + else + { + tyCont!.SubTypes.Add(subTypes.Key, subTypes.Value); + } + } + Types = tyCont!.SubTypes; + } + else + { + foreach (var subTypes in Types) + { + foreach (var sType in subTypes.Value) + { + sType.Delete(); + } + } + + return ret; + } + } + else + { + return (null, null); + } + + return Namespaces.Count > 0 || Types.Count > 0 ? ret : (null, null); + } + + public void Clean(string ns, SyntaxContext context, List usings) + { + ns = $"{(ns.Length > 0 ? $"{ns}." : "")}{Node.Name}"; + List removals = []; + + foreach (var nameSp in Namespaces) + { + if (nameSp.Value.DefinedTypeCount == 0) + { + removals.Add(nameSp.Key); + } + else + { + if (nameSp.Value is NamespaceContext nsc) + { + nsc.Parent = this; + } + nameSp.Value.Clean(ns, context, usings); + } + } + + foreach (var rem in removals) + { + Namespaces.Remove(rem); + } + + removals.Clear(); + foreach (var types in Types) + { + for (int i = 0; i < types.Value.Count; i++) + { + if (types.Value[i].IsNull) + { + types.Value.RemoveAt(i); + i--; + } + else if (types.Value[i].Type is TypeContext ty) + { + ty.Clean(ns, context, usings); + } + } + + if (types.Value.Count == 0) + { + removals.Add(types.Key); + } + } + + foreach (var rem in removals) + { + Types.Remove(rem); + } + } + + public void Merge(NamespaceContext other) + { + foreach (var ns in other.Namespaces) + { + if (Namespaces.ContainsKey(ns.Key)) + { + var oldNS = Namespaces[ns.Key]; + Namespaces[ns.Key] = ns.Value; + + ns.Value.Merge(oldNS); + } + else + { + Namespaces.Add(ns.Key, ns.Value); + } + } + } + + public void AddNamespace(BaseNamespaceDeclarationSyntax syntax, ContextCSharpSyntaxRewriter rewriter) + { + if (_CurrentLock == AddLock.Namespace) + { + tempNewNodeList.Add(syntax); + } + + string name = syntax.Name.ToString(); + if (Namespaces.ContainsKey(name)) + { + var oldNS = Namespaces[name]; + Namespaces[name] = new(rewriter.CurrentNamespace, syntax, rewriter.Context!, rewriter.Usings, this, rewriter.File); + + Namespaces[name].Merge(oldNS); + } + else + { + Namespaces.Add(name, new(rewriter.CurrentNamespace, syntax, rewriter.Context!, rewriter.Usings, this, rewriter.File)); + } + } + + public void ReconcileNamespaces(ContextCSharpSyntaxRewriter rewriter) + { + foreach (var syntax in tempNewNodeList) + { + if (syntax is BaseNamespaceDeclarationSyntax ns) + { + AddNamespace(ns, rewriter); + } + } + tempNewNodeList.Clear(); + } + + public void RemoveNamespace(string name) + { + Namespaces.Remove(name); + } + + public bool TryGetNamespace(string name, out INamespaceContext? ns) + { + var ret = Namespaces.TryGetValue(name, out var nameSp); + ns = nameSp; + return ret; + } + + public bool TryGetType(string typeName, out TypeContainer subType, int genericParameterCount = 0) + { + if (!Types.TryGetValue(typeName, out var value)) + { + subType = new(); + return false; + } + + foreach (var container in value) + { + if (container.Type is not null && container.Type.GenericParameterCount == genericParameterCount) + { + subType = container.ToTypeContainer(); + return true; + } + } + + subType = new(); + return false; + } + + public bool TryAddType(BaseTypeDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) + { + if (_CurrentLock == AddLock.Type) + { + tempNewNodeList.Add(node); + return true; + } + + var ty = new TypeContextContainer(rewriter.CurrentNamespaceContext, rewriter.Context!.CreateTypeContextFromNode(node, rewriter.CurrentNamespaceContext, rewriter.File, rewriter.Usings, null)!, node.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + rewriter.Context?.AddTypeContextContainer(ty); + + if (!Types.TryGetValue(node.Identifier.Text, out var list)) + { + list = [ty]; + Types.Add(node.Identifier.Text, list); + return true; + } + + for (int i = 0; i < list.Count; i++) + { + if (list[i].Type is not null && list[i].Type?.GenericParameterCount == ty.Type?.GenericParameterCount) + { + list[i] = ty; + return true; + } + } + list.Add(ty); + return true; + } + + public void ReconcileTypes(ContextCSharpSyntaxRewriter rewriter) + { + foreach (var syntax in tempNewNodeList) + { + if (syntax is BaseTypeDeclarationSyntax ty) + { + TryAddType(ty, rewriter); + } + else if (syntax is DelegateDeclarationSyntax del) + { + TryAddType(del, rewriter); + } + } + tempNewNodeList.Clear(); + } + + public bool TryAddType(DelegateDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) + { + if (_CurrentLock == AddLock.Type) + { + tempNewNodeList.Add(node); + return true; + } + + var d = new TypeContextContainer(rewriter.CurrentNamespaceContext, new DelegateContext(node, rewriter.CurrentNamespace, rewriter.Context!, rewriter.Usings, null), node.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + rewriter.Context?.AddTypeContextContainer(d); + + if (!Types.TryGetValue(node.Identifier.Text, out var list)) + { + list = [d]; + Types.Add(node.Identifier.Text, list); + return true; + } + + for (int i = 0; i < list.Count; i++) + { + if (list[i].Delegate is not null && list[i].Delegate?.GenericParameterCount == d.Delegate?.GenericParameterCount) + { + list[i] = d; + return true; + } + } + list.Add(d); + return true; + } + public void RemoveType(string name, int genericParameterCount = 0) + { + if (Types.TryGetValue(name, out var list)) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i].Type?.GenericParameterCount == genericParameterCount) + { + list.RemoveAt(i); + i--; + } + } + + if (list.Count == 0) + { + Types.Remove(name); + } + } + } + + public void RemoveTypes(string name) + { + Types.Remove(name); + } + + public int DefinedTypeCount => Types.Select(t => t.Value.Where(ty => ty.Type is not null).Select(ty => ty.Type is TypeContext type ? type.DefinedTypeCount : 1).Aggregate((a, b) => a + b)).Aggregate((a, b) => a + b) + Namespaces.Select(n => n.Value.DefinedTypeCount).Aggregate((a, b) => a + b); + + INamespaceContext? INamespaceContext.Parent => Parent; + + public string FullNamespace => $"{(Parent is null ? string.Empty : $"{Parent.FullNamespace}.")}{Name}"; + + public string Name => Node.Name.ToString(); + + BaseNamespaceDeclarationSyntax? INamespaceContext.Node => Node; + + IEnumerable<(string, INamespaceContext)> INamespaceContext.Namespaces => Namespaces.Select(kvp => (kvp.Key, (INamespaceContext)kvp.Value)); + + IEnumerable<(string, IEnumerable)> INamespaceContext.Types => Types.Select(kvp => (kvp.Key, kvp.Value.Select(type => type.ToTypeContainer()))); + + enum AddLock : byte + { + None, + Namespace, + Type + } + } + + private class TypeContext : IBaseTypeContext + { + public TypeContext(INamespaceContext? ns, string file, TypeDeclarationSyntax node, SyntaxContext context, List usings, IBaseTypeContext? parent = null) + { + ParentType = parent; + File = file; + Node = node.WithMembers(List(Array.Empty())); + + string parentName = parent is null ? string.Empty : parent.Name; + + foreach (var member in node.Members) + { + ProcessMember(member, ns, file, context, usings); + } + + if (node.BaseList is not null) + { + foreach (var baseType in node.BaseList.Types) + { + BaseTypes.Add(baseType.Type.ToString(), context.GetTypeContainer(baseType.Type, ns?.FullNamespace ?? string.Empty, usings, this, out int _, GenericParameters)); + } + } + } + + + public string File; + public TypeDeclarationSyntax Node; + public Dictionary BaseTypes = []; + public Dictionary> SubTypes = []; + public Dictionary> Methods = []; + public Dictionary Fields = []; + public Dictionary Properties = []; + public IBaseTypeContext? ParentType; + List tempNewNodeList = []; + bool _IsWalkingTypes = false; + + BaseTypeDeclarationSyntax? IBaseTypeContext.Node => Node; + + IEnumerable<(string, BaseTypeSyntax, IBaseTypeContext)> IBaseTypeContext.BaseTypes => BaseTypes.Where(bT => bT.Value.Type is not null).Select(bT => (bT.Key, (BaseTypeSyntax)SimpleBaseType(bT.Value.Type!.Syntax), bT.Value.Type!)); + + IEnumerable<(string, IEnumerable)> IBaseTypeContext.SubTypes => SubTypes.Select(types => (types.Key, types.Value.Select(type => type.ToTypeContainer()))); + + IEnumerable<(string, IBaseTypeContext.Field)> IBaseTypeContext.Fields => Fields.Select(field => (field.Key, new IBaseTypeContext.Field(field.Value.Node, field.Value.Container?.ToTypeContainer(), field.Value.PointerDepth))); + + IEnumerable<(string, IBaseTypeContext.Property)> IBaseTypeContext.Properties => Properties.Select(property => (property.Key, new IBaseTypeContext.Property(property.Value.Node, property.Value.Container?.ToTypeContainer(), property.Value.PointerDepth))); + + IEnumerable<(string, IEnumerable)> IBaseTypeContext.Methods => Methods.Select(methods => (methods.Key, + methods.Value.Select(method => new IBaseTypeContext.Method(method.Node, method.ReturnType.Item1?.ToTypeContainer(), method.ReturnType.Item2, + method.Parameters.Select(par => (par.Key, new IBaseTypeContext.MethodParameter(par.Value.Node, par.Value.Type.ToTypeContainer(), par.Value.PointerDepth))))))); + + public string Name => $"{(Parent is null ? string.Empty : $"{Parent.Name}.")}{Node.Identifier.Text}"; + + public IBaseTypeContext? Parent => ParentType; + + public TypeSyntax Syntax => IdentifierName(Node.Identifier.Text); + + public string FileName => File; + + public MemberDeclarationSyntax? ToCompletedNode() + { + List members = []; + foreach (string type in SubTypes.Keys) + { + members.AddRange(SubTypes[type].Where(t => t.Type is not null).Select(t => t.Type!.ToCompletedNode()!)); + } + foreach (string method in Methods.Keys) + { + members.AddRange(Methods[method].Select(m => m.Node)); + } + members.AddRange(Fields.Select(f => f.Value.ToCompletedNode())); + members.AddRange(Properties.Select(p => p.Value.ToCompletedNode())); + return Node.WithMembers(List(members)) + .WithBaseList( + BaseList( + SeparatedList(BaseTypes.Select(bType => (BaseTypeSyntax)SimpleBaseType(bType.Value.Type!.Syntax))))); + } + + public int DefinedTypeCount => SubTypes.Select(t => t.Value.Where(ty => ty.Type is not null).Select(ty => ty.Type is TypeContext type ? type.DefinedTypeCount : 1).Aggregate((a, b) => a + b)).Aggregate((a, b) => a + b) + 1; + + public int GenericParameterCount => Node.TypeParameterList is null ? 0 : Node.TypeParameterList.Parameters.Count; + + public IEnumerable GenericParameters => ((IEnumerable?)Node.TypeParameterList?.Parameters ?? Array.Empty()).Concat(Parent?.GenericParameters ?? Array.Empty()); + public void Clean(string ns, SyntaxContext context, List usings) + { + List removals = []; + foreach (var types in SubTypes) + { + for (int i = 0; i < types.Value.Count; i++) + { + if (types.Value[i].IsNull) + { + types.Value.RemoveAt(i); + i--; + } + else if (types.Value[i].Type is TypeContext ty) + { + ty.Clean(ns, context, usings); + } + else if (types.Value[i].Delegate is DelegateContext del) + { + del.RefreshTypeLinks(ns, context, usings, this); + } + } + + if (types.Value.Count == 0) + { + removals.Add(types.Key); + } + } + + foreach (var field in Fields) + { + field.Value.RefreshTypeLinks(ns, context, usings, this); + } + + foreach (var prop in Properties) + { + prop.Value.RefreshTypeLinks(ns, context, usings, this); + } + + foreach (var methods in Methods) + { + foreach (var method in methods.Value) + { + method.RefreshTypeLinks(ns, context, usings, this); + } + } + + foreach (var rem in removals) + { + SubTypes.Remove(rem); + } + } + + public bool TryGetSubType(string typeName, out TypeContainer subType, int genericParameterCount = 0) + { + if (!SubTypes.TryGetValue(typeName, out var value)) + { + subType = new(); + return false; + } + + foreach (var container in value) + { + if (container.Type is not null && container.Type.GenericParameterCount == genericParameterCount) + { + subType = container.ToTypeContainer(); + return true; + } + } + + subType = new(); + return false; + } + + public bool TryGetField(string fieldName, out IBaseTypeContext.Field field) + { + if (!Fields.TryGetValue(fieldName, out var context)) + { + field = new(); + return false; + } + + field = new(context.Node, context.Container?.ToTypeContainer(), context.PointerDepth); + return true; + } + + public bool TryGetProperty(string propertyName, out IBaseTypeContext.Property property) + { + if (!Properties.TryGetValue(propertyName, out var context)) + { + property = new(); + return false; + } + + property = new(context.Node, context.Container?.ToTypeContainer(), context.PointerDepth); + return true; + } + + public bool HasBaseType(string baseType) + { + return BaseTypes.ContainsKey(baseType); + } + + public void Visit(ContextCSharpSyntaxVisitor visitor) + { + var oldContext = visitor.CurrentContext; + visitor.CurrentContext = this; + + foreach (var subTypes in SubTypes) + { + foreach (var subType in subTypes.Value) + { + subType.Type?.Visit(visitor); + } + } + + foreach (var field in Fields) + { + visitor.Visit(field.Value.Node); + } + + foreach (var property in Properties) + { + visitor.Visit(property.Value.Node); + } + + foreach (var methods in Methods) + { + foreach (var method in methods.Value) + { + visitor.Visit(method.Node); + } + } + + visitor.Visit(Node); + + visitor.CurrentContext = oldContext; + } + + public TypeContainer? Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file) + { + var oldContext = rewriter.CurrentContext; + rewriter.CurrentContext = new(this); + + List removals = []; + Dictionary> newTypes = []; + _IsWalkingTypes = true; + foreach (var members in SubTypes) + { + for (int i = 0; i < members.Value.Count; i++) + { + var member = members.Value[i]; + if (member.Type is null) + { + continue; + } + member.ConvertContainer(member.Type.Rewrite(rewriter, ns, file)); + + if (member.IsNull) + { + members.Value.RemoveAt(i); + i--; + continue; + } + + if (members.Key != member.Name) + { + members.Value.RemoveAt(i); + i--; + + if (SubTypes.TryGetValue(member.Name, out var list2)) + { + list2.Add(member); + } + else + { + newTypes.Add(member.Name, [member]); + } + + rewriter.Context!.RenameType(member, members.Key); + } + } + + if (members.Value.Count == 0) + { + removals.Add(members.Key); + continue; + } + } + + foreach (var rem in removals) + { + SubTypes.Remove(rem); + } + + foreach (var newMember in newTypes) + { + SubTypes.Add(newMember.Key, newMember.Value); + } + + _IsWalkingTypes = false; + ReconcileTypes(rewriter); + + List newMembers = new List(); + foreach (var field in Fields) + { + var newMember = rewriter.Visit(field.Value.Node); + + if (newMember is MemberDeclarationSyntax member) + { + newMembers.Add(member); + } + } + + foreach (var property in Properties) + { + var newMember = rewriter.Visit(property.Value.Node); + + if (newMember is MemberDeclarationSyntax member) + { + newMembers.Add(member); + } + } + + foreach (var methods in Methods) + { + foreach (var method in methods.Value) + { + var newMember = rewriter.Visit(method.Node); + + if (newMember is MemberDeclarationSyntax member) + { + newMembers.Add(member); + } + } + } + + Methods.Clear(); + Fields.Clear(); + Properties.Clear(); + + foreach (var member in newMembers) + { + ProcessMember(member, rewriter.CurrentNamespaceContext, file, rewriter.Context!, rewriter.Usings); + } + + var newNode = rewriter.Visit(Node); + + if (newNode is null) + { + rewriter.CurrentContext = oldContext; + return null; + } + + if (newNode is EnumDeclarationSyntax en) + { + var newContext = new TypeContainer(Enum:new EnumContext(file, en, Parent)); + + rewriter.CurrentContext = oldContext; + return newContext; + } + else if (newNode is TypeDeclarationSyntax ty) + { + Node = ty; + + foreach (var member in Node.Members) + { + ProcessMember(member, rewriter.CurrentNamespaceContext, file, rewriter.Context!, rewriter.Usings); + } + + if (Node.BaseList is not null) + { + foreach (var baseType in Node.BaseList.Types) + { + TryAddBaseType(baseType, rewriter.Context!, rewriter); + } + } + + Node = Node.WithMembers(List(Array.Empty())).WithBaseList(BaseList(SeparatedList(Array.Empty()))); + } + else if (newNode is DelegateDeclarationSyntax del) + { + var newContext = new TypeContainer(Delegate: new DelegateContext(del, rewriter.CurrentNamespace, rewriter.Context!, rewriter.Usings, Parent)); + + rewriter.CurrentContext = oldContext; + return newContext; + } + else + { + rewriter.CurrentContext = oldContext; + throw new Exception("Type Declarations cannot be replaced with non type declarations"); + } + + rewriter.CurrentContext = oldContext; + return new(this); + } + + private void ProcessMember(MemberDeclarationSyntax member, INamespaceContext? ns, string file, SyntaxContext context, List usings) + { + string parentName = Parent is null ? string.Empty : Parent.Name; + if (member is BaseTypeDeclarationSyntax bT) + { + var ty = new TypeContextContainer(ns, context.CreateTypeContextFromNode(bT, ns, file, usings, this)!, bT.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + + context.AddTypeContextContainer(ty); + + if (!SubTypes.TryGetValue(bT.Identifier.Text, out var list)) + { + list = []; + SubTypes.Add(bT.Identifier.Text, list); + } + list.Add(ty); + } + else if (member is MethodDeclarationSyntax m) + { + string name = m.Identifier.Text; + if (!Methods.TryGetValue(name, out var methods)) + { + methods = new(); + Methods.Add(name, methods); + } + methods.Add(new(ns?.FullNamespace ?? string.Empty, m, context, usings, parentName, this)); + } + else if (member is BaseFieldDeclarationSyntax f) + { + TypeContextContainer? type = context.GetTypeContainer(f.Declaration.Type, ns?.FullNamespace ?? string.Empty, usings, this, out int pDepth, GenericParameters, parentName); + + foreach (var dec in f.Declaration.Variables) + { + Fields.Add(dec.Identifier.Text, new(type, pDepth, f.WithDeclaration(f.Declaration.WithVariables(SeparatedList(new[] { dec }))))); + } + } + else if (member is BasePropertyDeclarationSyntax p) + { + TypeContextContainer? type = context.GetTypeContainer(p.Type, ns?.FullNamespace ?? string.Empty, usings, this, out int pDepth, GenericParameters, parentName); + + string propName = "[]"; + if (member is PropertyDeclarationSyntax prop) + { + propName = prop.Identifier.Text; + } + else if (member is EventDeclarationSyntax even) + { + propName = even.Identifier.Text; + } + Properties.Add(propName, new(type, pDepth, p)); + } + else if (member is DelegateDeclarationSyntax del) + { + var d = new TypeContextContainer(ns, new DelegateContext(del, ns?.FullNamespace ?? string.Empty, context, usings, this), del.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + + context.AddTypeContextContainer(d); + + if (!SubTypes.TryGetValue(del.Identifier.Text, out var list)) + { + list = []; + SubTypes.Add(del.Identifier.Text, list); + } + list.Add(d); + } + } + + public bool TryAddBaseType(BaseTypeSyntax baseType, SyntaxContext context, ContextCSharpSyntaxRewriter rewriter) + { + if (BaseTypes.ContainsKey(baseType.Type.ToString())) + { + BaseTypes[baseType.Type.ToString()] = context.GetTypeContainer(baseType.Type, rewriter.CurrentNamespace, rewriter.Usings, this, out int _, GenericParameters); + } + else + { + BaseTypes.Add(baseType.Type.ToString(), context.GetTypeContainer(baseType.Type, rewriter.CurrentNamespace, rewriter.Usings, this, out int _, GenericParameters)); + } + return true; + } + + public void RemoveBaseType(BaseTypeSyntax baseType) + { + BaseTypes.Remove(baseType.Type.ToString()); + } + + public bool TryAddSubType(BaseTypeDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) + { + if (_IsWalkingTypes) + { + tempNewNodeList.Add(node); + return true; + } + + ProcessMember(node, rewriter.CurrentNamespaceContext, File, rewriter.Context!, rewriter.Usings); + return true; + } + public void RemoveSubType(string name, int genericParameterCount = 0) + { + if (SubTypes.TryGetValue(name, out var list)) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i].Type?.GenericParameterCount == genericParameterCount) + { + list.RemoveAt(i); + i--; + } + } + + if (list.Count == 0) + { + SubTypes.Remove(name); + } + } + } + + public void RemoveSubTypes(string name) + { + SubTypes.Remove(name); + } + public bool TryAddField(BaseFieldDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) + { + ProcessMember(node, rewriter.CurrentNamespaceContext, File, rewriter.Context!, rewriter.Usings); + return true; + } + public void RemoveField(string name) + { + Fields.Remove(name); + } + public bool TryAddProperty(BasePropertyDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) + { + ProcessMember(node, rewriter.CurrentNamespaceContext, File, rewriter.Context!, rewriter.Usings); + return true; + } + public void RemoveProperty(string name) + { + Properties.Remove(name); + } + public bool HasBaseType(BaseTypeSyntax baseType) => BaseTypes.Any(bT => bT.Value.Type?.Syntax == baseType.Type); + public void RemoveBaseType(string baseType) + { + BaseTypes.Remove(baseType); + } + public bool TryGetMethods(string name, out IEnumerable methodInfo) + { + if (!Methods.TryGetValue(name, out var methods)) + { + methodInfo = Array.Empty(); + return false; + } + methodInfo = methods.Select(method => new IBaseTypeContext.Method(method.Node, method.ReturnType.Item1?.ToTypeContainer(), method.ReturnType.Item2, + method.Parameters.Select(par => (par.Key, new IBaseTypeContext.MethodParameter(par.Value.Node, par.Value.Type?.ToTypeContainer(), par.Value.PointerDepth))))); + return true; + } + public bool TryAddMethod(MethodDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) + { + ProcessMember(node, rewriter.CurrentNamespaceContext, File, rewriter.Context!, rewriter.Usings); + return true; + } + public void RemoveMethod(string name, params TypeSyntax[] parameters) + { + if (Methods.TryGetValue(name, out var list)) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i].Parameters.Select(par => par.Value.Node.Type).SequenceEqual(parameters)) + { + list.RemoveAt(i); + i--; + } + } + + if (list.Count == 0) + { + Methods.Remove(name); + } + } + } + public void RemoveMethods(string name) + { + Methods.Remove(name); + } + + public void SetParent(IBaseTypeContext? parent) => ParentType = parent; + public void Delete() + { + foreach (var subTypes in SubTypes) + { + foreach (var subType in subTypes.Value) + { + subType.Delete(); + } + } + } + + public bool TryAddSubType(DelegateDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) + { + if (_IsWalkingTypes) + { + tempNewNodeList.Add(node); + return true; + } + var d = new TypeContextContainer(rewriter.CurrentNamespaceContext, new DelegateContext(node, rewriter.CurrentNamespace, rewriter.Context!, rewriter.Usings, this), node.Modifiers + .Where(token => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.PrivateKeyword)) + .Select(token => token.Kind()) + .FirstOrDefault(SyntaxKind.PrivateKeyword)); + + rewriter.Context?.AddTypeContextContainer(d); + + if (!SubTypes.TryGetValue(node.Identifier.Text, out var list)) + { + list = [ d ]; + SubTypes.Add(node.Identifier.Text, list); + return true; + } + + for (int i = 0; i < list.Count; i ++) + { + if (list[i].Delegate is not null && list[i].Delegate?.GenericParameterCount == d.Delegate?.GenericParameterCount) + { + list[i] = d; + return true; + } + } + list.Add(d); + return true; + } + + public void ReconcileTypes(ContextCSharpSyntaxRewriter rewriter) + { + foreach (var syntax in tempNewNodeList) + { + if (syntax is BaseTypeDeclarationSyntax ty) + { + TryAddSubType(ty, rewriter); + } + else if (syntax is DelegateDeclarationSyntax del) + { + TryAddSubType(del, rewriter); + } + } + tempNewNodeList.Clear(); + } + + enum AddLock + { + None, + Type, + Method, + Property, + Field + } + } + + private class EnumContext : IEnumTypeContext + { + public EnumContext(string file, EnumDeclarationSyntax node, IBaseTypeContext? parent) + { + ParentType = parent; + File = file; + Node = node.WithMembers(SeparatedList(Array.Empty())); + + foreach (var member in node.Members) + { + Members.Add(member.Identifier.Text, new(member)); + } + } + + public EnumDeclarationSyntax Node; + + public string File; + public IBaseTypeContext? ParentType; + public TypeSyntax Syntax => IdentifierName(Node.Identifier.Text); + + public Dictionary Members = []; + + bool IsWalkingEnumMembers = false; + List tempNewNodesList = []; + + EnumDeclarationSyntax? IEnumTypeContext.Node => Node; + + public IEnumerable<(string, EnumMemberDeclarationSyntax)> EnumMembers => Members.Select(em => (em.Key, em.Value.Node)); + public IEnumerable<(string, BaseTypeSyntax, IBaseTypeContext)> BaseTypes => Node.BaseList is null ? Array.Empty<(string, BaseTypeSyntax, IBaseTypeContext)>() : Node.BaseList.Types.Select(type => (type.Type.ToString(), type, (IBaseTypeContext)new UnknownTypeContext(type.Type))); + + public string FileName => File; + + public string Name => $"{(Parent is null ? string.Empty : $"{Parent.Name}.")}{Node.Identifier.Text}"; + + public IBaseTypeContext? Parent => ParentType; + + public bool IsEnum => true; + + public int GenericParameterCount => 0; + + public void Visit(ContextCSharpSyntaxVisitor visitor) + { + visitor.Visit(Node); + + foreach (var member in Members) + { + visitor.Visit(member.Value.Node); + } + } + + public TypeContainer? Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file) + { + var oldContext = rewriter.CurrentContext; + rewriter.CurrentContext = new(Enum:this); + + IsWalkingEnumMembers = true; + List removals = []; + foreach (var member in Members) + { + var node = rewriter.Visit(member.Value.Node); + + if (node is not EnumMemberDeclarationSyntax em) + { + removals.Add(member.Key); + continue; + } + + member.Value.Node = em; + } + IsWalkingEnumMembers = false; + + foreach (var rem in removals) + { + Members.Remove(rem); + } + + ReconcileEnumMembers(); + + Members = Members.ToDictionary(kvp => kvp.Value.Node.Identifier.Text, kvp => kvp.Value); + + var newNode = rewriter.Visit(Node); + + if (newNode is null) + { + rewriter.CurrentContext = oldContext; + return null; + } + + if (newNode is EnumDeclarationSyntax en) + { + Node = en; + + foreach (var member in Node.Members) + { + if (Members.ContainsKey(member.Identifier.Text)) + { + Members[member.Identifier.Text] = new(member); + } + else + { + Members.Add(member.Identifier.Text, new(member)); + } + } + + Node = Node.WithMembers(SeparatedList(Array.Empty())); + } + else if (newNode is TypeDeclarationSyntax ty) + { + var newContext = new TypeContext(rewriter.CurrentNamespaceContext, file, ty, rewriter.Context!, rewriter.Usings, ParentType); + + rewriter.CurrentContext = oldContext; + return new(newContext); + } + else if (newNode is DelegateDeclarationSyntax del) + { + var newContext = new TypeContainer(Delegate: new DelegateContext(del, rewriter.CurrentNamespace, rewriter.Context!, rewriter.Usings, ParentType)); + + rewriter.CurrentContext = oldContext; + return newContext; + } + else + { + throw new Exception("Type Declarations cannot be replaced with non type declarations"); + } + + var originalContext = rewriter.CurrentContext; + rewriter.CurrentContext = oldContext; + return originalContext; + } + + public MemberDeclarationSyntax? ToCompletedNode() => Node.WithMembers(SeparatedList(Members.Select(em => em.Value.Node))); + + public bool TryGetEnumMember(string memberName, out EnumMemberDeclarationSyntax? member) + { + if (!Members.TryGetValue(memberName, out var context)) + { + member = null; + return false; + } + + member = context.Node; + return true; + } + + public bool HasBaseType(string baseType) + { + return Node.BaseList is not null && Node.BaseList.Types.Any(type => type.Type.ToString() == baseType); + } + public bool HasBaseType(BaseTypeSyntax baseType) + { + return Node.BaseList is not null && Node.BaseList.Types.Any(type => type == baseType); + } + + public bool TryAddEnumMember(EnumMemberDeclarationSyntax node) + { + if (IsWalkingEnumMembers) + { + tempNewNodesList.Add(node); + return true; + } + + if (Members.ContainsKey(node.Identifier.Text)) + { + Members[node.Identifier.Text] = new(node); + } + else + { + Members.Add(node.Identifier.Text, new(node)); + } + return true; + } + + public void ReconcileEnumMembers() + { + foreach (var node in tempNewNodesList) + { + TryAddEnumMember(node); + } + } + + public void RemoveEnumMember(string name) + { + Members.Remove(name); + } + + public void SetParent(IBaseTypeContext? parent) => ParentType = parent; + } + + + private class UnknownTypeContext : IBaseTypeContext + { + public UnknownTypeContext(TypeSyntax type) + { + Type = type; + } + TypeSyntax Type; + + public TypeSyntax Syntax => Type; + + public string FileName => string.Empty; + + public string Name => Type.ToString(); + + public IBaseTypeContext? Parent => null; + + public BaseTypeDeclarationSyntax? Node => null; + + public int GenericParameterCount => Type is GenericNameSyntax generic ? generic.TypeArgumentList.Arguments.Count : 0; + + public IEnumerable<(string, BaseTypeSyntax, IBaseTypeContext)> BaseTypes => Array.Empty<(string, BaseTypeSyntax, IBaseTypeContext)>(); + + public IEnumerable<(string, IEnumerable)> SubTypes => Array.Empty<(string, IEnumerable)>(); + + public IEnumerable<(string, IBaseTypeContext.Field)> Fields => Array.Empty<(string, IBaseTypeContext.Field)>(); + + public IEnumerable<(string, IBaseTypeContext.Property)> Properties => Array.Empty<(string, IBaseTypeContext.Property)>(); + + public IEnumerable<(string, IEnumerable)> Methods => Array.Empty<(string, IEnumerable)>(); + + public IEnumerable GenericParameters => Parent?.GenericParameters ?? Array.Empty(); + + public MemberDeclarationSyntax? ToCompletedNode() + { + return null; + } + + public void Visit(ContextCSharpSyntaxVisitor visitor) + { + visitor.Visit(Node); + } + + public TypeContainer? Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file) + { + var oldContext = rewriter.CurrentContext; + + rewriter.CurrentContext = new(this); + + var type = rewriter.Visit(Type) as TypeSyntax; + + if (type is not null) + { + rewriter.CurrentContext = oldContext; + + return new(new UnknownTypeContext(type)); + } + + rewriter.CurrentContext = oldContext; + return null; + } + + public bool TryGetSubType(string typeName, out TypeContainer subType, int genericParameterCount = 0) + { + subType = new(); + return false; + } + public bool TryAddSubType(BaseTypeDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) => false; + public void RemoveSubType(string name, int genericParameterCount = 0) { } + public void RemoveSubTypes(string name) { } + public bool TryGetField(string fieldName, out IBaseTypeContext.Field field) + { + field = new(); + return false; + } + + public bool TryAddField(BaseFieldDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) => false; + public void RemoveField(string name) { } + public bool TryGetProperty(string propertyName, out IBaseTypeContext.Property property) + { + property = new(); + return false; + } + public bool TryAddProperty(BasePropertyDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) => false; + public void RemoveProperty(string name) { } + public bool HasBaseType(string baseType) => false; + public bool HasBaseType(BaseTypeSyntax baseType) => false; + public bool TryAddBaseType(BaseTypeSyntax baseType, SyntaxContext context, ContextCSharpSyntaxRewriter rewriter) => false; + public void RemoveBaseType(string baseType) { } + public bool TryGetMethods(string name, out IEnumerable methodInfo) + { + methodInfo = Array.Empty(); + return false; + } + public bool TryAddMethod(MethodDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) => false; + public void RemoveMethod(string name, params TypeSyntax[] parameters) { } + public void RemoveMethods(string name) { } + + public void SetParent(IBaseTypeContext? parent) { } + + public void Delete() { } + + public bool TryAddSubType(DelegateDeclarationSyntax node, ContextCSharpSyntaxRewriter rewriter) => false; + + public enum TypeLocation + { + BaseList, + Field, + Property + } + } + + private class DelegateContext : LeafNodeContext, IDelegateContext + { + public DelegateContext(DelegateDeclarationSyntax node, string ns, SyntaxContext context, List usings, IBaseTypeContext? parent) : base(node) + { + ParentType = parent; + + int pDepth; + foreach (var para in node.ParameterList.Parameters) + { + Parameters.Add(para.Identifier.Text, new(para, ns, context, usings, parent?.Name ?? string.Empty, ParentType as TypeContext)); + } + + ReturnType = (context.GetTypeContainer(node.ReturnType, ns, usings, parent as TypeContext, out pDepth, parent?.GenericParameters ?? Array.Empty(), parent?.Name ?? string.Empty), pDepth); + } + + public void RefreshTypeLinks(string ns, SyntaxContext context, List usings, IBaseTypeContext? parent) + { + int pDepth; + Parameters.Clear(); + foreach (var para in Node.ParameterList.Parameters) + { + Parameters.Add(para.Identifier.Text, new(para, ns, context, usings, parent?.Name ?? string.Empty, ParentType as TypeContext)); + } + + ReturnType = (context.GetTypeContainer(Node.ReturnType, ns, usings, parent as TypeContext, out pDepth, parent?.GenericParameters ?? Array.Empty(), parent?.Name ?? string.Empty), pDepth); + } + + public Dictionary Parameters = []; + public (TypeContextContainer?, int) ReturnType; + public IBaseTypeContext? ParentType; + + public IBaseTypeContext? Parent => ParentType; + + public string Name => $"{(ParentType is null ? "" : $"{ParentType.Name}.")}{Node.Identifier.Text}"; + + DelegateDeclarationSyntax? IDelegateContext.Node => Node; + + IEnumerable<(string, IBaseTypeContext.MethodParameter)> IDelegateContext.Parameters => Parameters.Select(par => (par.Key, new IBaseTypeContext.MethodParameter(par.Value.Node, par.Value.Type?.ToTypeContainer(), par.Value.PointerDepth))); + + public int GenericParameterCount => Node.TypeParameterList?.Parameters.Count ?? 0; + + public static bool operator ==(DelegateContext left, DelegateContext right) + { + return left.Name == right.Name && + left.Parameters.Values.SequenceEqual(right.Parameters.Values); + } + + public static bool operator !=(DelegateContext left, DelegateContext right) + { + return left.Name != right.Name || + !left.Parameters.Values.SequenceEqual(right.Parameters.Values); + } + + public override bool Equals(object? obj) => base.Equals(obj); + + public override int GetHashCode() => ToString().GetHashCode(); + + public override string ToString() + { + return $"{Node.Identifier.Text}({string.Join(',', Parameters.Select(par => $"{par.Value} {par.Key}"))})"; + } + + public static DelegateContext? ToDelegateContext(IDelegateContext context) + { + return context as DelegateContext; + } + + public TypeContainer? Rewrite(ContextCSharpSyntaxRewriter rewriter, string ns, string file) + { + var node = rewriter.Visit(Node); + + if (node is DelegateDeclarationSyntax del) + { + Node = del; + RefreshTypeLinks(ns, rewriter.Context!, rewriter.Usings, Parent); + } + else if (node is EnumDeclarationSyntax en) + { + var newContext = new TypeContainer(Enum: new EnumContext(file, en, Parent)); + + return newContext; + } + else if (node is TypeDeclarationSyntax ty) + { + var newContext = new TypeContext(rewriter.CurrentNamespaceContext, file, ty, rewriter.Context!, rewriter.Usings, ParentType); + return new(newContext); + } + else if (node is not null) + { + throw new Exception("Delegates cannot be replaced by non-type declarations (enum, class, delegate, etc.)"); + } + + return null; + } + + public void Visit(ContextCSharpSyntaxVisitor visitor) + { + visitor.Visit(Node); + } + } + + internal class TypeContextContainer + { + public TypeContextContainer(INamespaceContext? ns, IBaseTypeContext? ty, IEnumTypeContext? en, IDelegateContext? del, SyntaxKind visibility) + { + Namespace = ns; + Type = ty; + Visibility = visibility; + Delegate = del; + Enum = en; + } + + public TypeContextContainer(INamespaceContext? ns, TypeContainer? container, SyntaxKind visibility) + { + Namespace = ns; + Type = container?.Type; + Delegate = container?.Delegate; + Enum = container?.Enum; + Visibility = visibility; + } + + public TypeContextContainer(INamespaceContext? ns, IBaseTypeContext? ty, SyntaxKind visibility) + { + Namespace = ns; + Type = ty; + Visibility = visibility; + Delegate = null; + Enum = null; + } + + public TypeContextContainer(INamespaceContext? ns, IEnumTypeContext? en, SyntaxKind visibility) + { + Namespace = ns; + Type = null; + Visibility = visibility; + Delegate = null; + Enum = en; + } + + public TypeContextContainer(INamespaceContext? ns, IDelegateContext? del, SyntaxKind visibility) + { + Namespace = ns; + Type = null; + Visibility = visibility; + Delegate = del; + Enum = null; + } + + public INamespaceContext? Namespace; + public string FullNamespace => Namespace?.FullNamespace ?? string.Empty; + public SyntaxKind Visibility; + public IBaseTypeContext? Type; + public IEnumTypeContext? Enum; + public IDelegateContext? Delegate; + + public string Name => Type is not null ? Type.Name : (Enum is not null ? Enum.Name : (Delegate is not null ? Delegate.Name : string.Empty)); + + public void SetParent(IBaseTypeContext? parent, SyntaxContext context) + { + if (Type is not null) + { + string name = Type.Name; + Type.SetParent(parent); + + context.RenameType(this, name); + } + } + + public bool IsPublic => Visibility == SyntaxKind.PublicKeyword; + + public bool IsNull => Type is null && Enum is null && Delegate is null; + + public override string ToString() + { + return Type?.Syntax.ToString() ?? string.Empty; + } + + public TypeContainer ToTypeContainer() + { + return new(Type, Enum, Delegate); + } + + public void ConvertContainer(TypeContainer? container) + { + Type = container?.Type; + Enum = container?.Enum; + Delegate = container?.Delegate; + } + + public void Delete() + { + Type?.Delete(); + Type = null; + Enum = null; + Delegate = null; + } + } + + private class EnumMemberContext : LeafNodeContext + { + public EnumMemberContext(EnumMemberDeclarationSyntax node) : base(node) { } + } + + private class MethodContext : LeafNodeContext + { + public MethodContext(string ns, MethodDeclarationSyntax node, SyntaxContext context, List usings, string parentName, TypeContext type) : base(node) + { + int pDepth; + foreach (var para in node.ParameterList.Parameters) + { + Parameters.Add(para.Identifier.Text, new(para, ns, context, usings, parentName, type)); + } + + ReturnType = (context.GetTypeContainer(node.ReturnType, ns, usings, type, out pDepth, type?.GenericParameters ?? Array.Empty(), parentName), pDepth); + } + + public void RefreshTypeLinks(string ns, SyntaxContext context, List usings, IBaseTypeContext? parent) + { + if (parent is not TypeContext ty) + { + return; + } + int pDepth; + Parameters.Clear(); + foreach (var para in Node.ParameterList.Parameters) + { + Parameters.Add(para.Identifier.Text, new(para, ns, context, usings, parent?.Name ?? string.Empty, ty)); + } + + ReturnType = (context.GetTypeContainer(Node.ReturnType, ns, usings, ty, out pDepth, parent?.GenericParameters ?? Array.Empty(), parent?.Name ?? string.Empty), pDepth); + } + + public Dictionary Parameters = []; + public (TypeContextContainer?, int) ReturnType; + + public override string ToString() + { + return $"{Node.Identifier.Text}({string.Join(',', Parameters.Select(par => $"{par.Value} {par.Key}"))})"; + } + + public static bool operator ==(MethodContext left, MethodContext right) + { + return left.Node.Identifier.Text == right.Node.Identifier.Text && + left.Parameters.Values.SequenceEqual(right.Parameters.Values); + } + + public static bool operator !=(MethodContext left, MethodContext right) + { + return left.Node.Identifier.Text != right.Node.Identifier.Text || + !left.Parameters.Values.SequenceEqual(right.Parameters.Values); + } + + public override bool Equals(object? obj) => base.Equals(obj); + + public override int GetHashCode() => ToString().GetHashCode(); + } + + private class MethodParameterContext : LeafNodeContext + { + public MethodParameterContext(ParameterSyntax node, string ns, SyntaxContext context, List usings, string parentName, TypeContext? type) : base(node) + { + Type = context.GetTypeContainer(node.Type!, ns, usings, type, out PointerDepth, type?.GenericParameters ?? Array.Empty(), parentName); + } + + public TypeContextContainer Type; + public int PointerDepth; + } + + private class FieldContext : VariableNodes + { + public FieldContext(TypeContextContainer container, int pointerDepth, BaseFieldDeclarationSyntax node) : base(container, node) { PointerDepth = pointerDepth; } + + public void RefreshTypeLinks(string ns, SyntaxContext context, List usings, IBaseTypeContext? parent) + { + if (parent is TypeContext type) + { + var genericTypes = type.GenericParameters; + + Container = context.GetTypeContainer(Node.Declaration.Type, ns, usings, type, out PointerDepth, parent?.GenericParameters ?? Array.Empty(), parent?.Name ?? string.Empty); + } + } + + public int PointerDepth; + + public BaseFieldDeclarationSyntax ToCompletedNode() + { + if (Container is null) + { + return Node; + } + var type = Container.Type!.Syntax; + + int pDepth = PointerDepth; + while (pDepth > 0) + { + type = PointerType(type); + pDepth--; + } + + return Node.WithDeclaration(Node.Declaration.WithType(type)); + } + } + + private class PropertyContext : VariableNodes + { + public PropertyContext(TypeContextContainer container, int pointerDepth, BasePropertyDeclarationSyntax node) : base(container, node) { PointerDepth = pointerDepth; } + + public void RefreshTypeLinks(string ns, SyntaxContext context, List usings, IBaseTypeContext? parent) + { + if (parent is TypeContext type) + { + Container = context.GetTypeContainer(Node.Type, ns, usings, type, out PointerDepth, parent?.GenericParameters ?? Array.Empty(), parent?.Name ?? string.Empty); + } + } + + public int PointerDepth; + + public BasePropertyDeclarationSyntax ToCompletedNode() + { + if (Container is null) + { + return Node; + } + var type = Container.Type!.Syntax; + + int pDepth = PointerDepth; + while (pDepth > 0) + { + type = PointerType(type); + pDepth--; + } + + return Node.WithType(type); + } + } + + private class VariableNodes : LeafNodeContext + where TNodeType : SyntaxNode + { + public VariableNodes(TypeContextContainer container, TNodeType node) : base(node) + { + Container = container; + } + + public TypeContextContainer Container; + } + + private class LeafNodeContext(TNodeType node) + where TNodeType: SyntaxNode + { + public TNodeType Node = node; + + public SyntaxNode? Rewrite(CSharpSyntaxRewriter rewriter) => rewriter.Visit(Node); + } + + /// + /// checks if ns1 is a child namespace of ns2 + /// + /// + /// + /// + private static bool NamespaceMatch(string ns1, string ns2) + { + for (int i = 0; i < ns2.Length; i++) + { + if (ns2[i] != ns1[i]) + return false; + } + return true; + } +} diff --git a/sources/SilkTouch/Clang/TypeContainer.cs b/sources/SilkTouch/Clang/TypeContainer.cs new file mode 100644 index 0000000000..65c888ee4e --- /dev/null +++ b/sources/SilkTouch/Clang/TypeContainer.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Silk.NET.SilkTouch.Clang; + +/// +/// A container to represent a potential Type/Delegate since often the two can be used interchangably +/// +/// +/// +/// +public record struct TypeContainer(IBaseTypeContext? Type = null, IEnumTypeContext? Enum = null, IDelegateContext? Delegate = null); diff --git a/sources/SilkTouch/Mods/AddApiProfiles.cs b/sources/SilkTouch/Mods/AddApiProfiles.cs index f1125f50c4..96eea5a62d 100644 --- a/sources/SilkTouch/Mods/AddApiProfiles.cs +++ b/sources/SilkTouch/Mods/AddApiProfiles.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading.Tasks; using Humanizer; @@ -61,11 +62,11 @@ class Rewriter( string? jobKey, IEnumerable< IApiMetadataProvider> - > versionProviders + > versionProviders, + Dictionary aggregatedUsings, + Configuration cfg ) : ModCSharpSyntaxRewriter { - public BakeSet? Baked { get; set; } - public ApiProfileDecl? Profile { get; set; } public ILogger? Logger { get; set; } @@ -287,7 +288,7 @@ ConversionOperatorDeclarationSyntax node where T : BaseFieldDeclarationSyntax { var retNode = @base(node); - if (Baked is null && Profile is not null && retNode is T ret) + if (Profile is not null && retNode is T ret) { // When we're in baking mode, all of the baking logic is done in VisitVariableDeclarator. However this // does nothing in the non-baking case, where we should add the attribute to the field as a marker. @@ -321,75 +322,11 @@ ConversionOperatorDeclarationSyntax node return retNode; } - if (Baked is null) - { - return retNode is T ret + return retNode is T ret ? ret.AddAttributeLists( AttributeList(SingletonSeparatedList(GetProfileAttribute(null))) ) : retNode; - } - - // Get this type's bake set for some final validation checks. - // This also registers the bake set if the type had no members. - var bakeSet = GetOrRegisterTypeBakeSet(node); - - // If this is a struct or record struct, fields should exactly match. - if ( - ( - node is StructDeclarationSyntax s - ? (SyntaxList?)s.Members - : node is RecordDeclarationSyntax r - && r.Modifiers.Any(SyntaxKind.StructKeyword) - ? r.Members - : null - ) - is not { } members - ) - { - // If it's not a struct or record struct, we've registered the type and that's all we really need to do. - // Any members will have been registered in the base call. - return null; // baking erases, but the caller should know that. - } - - var bakedFields = bakeSet - .Children.OrderBy(x => x.Value.Index) - .Select(x => x.Value.Syntax) - .OfType() - .SelectMany(x => x.Declaration.Variables.Select(y => (x.Declaration.Type, Var: y))) - .ToArray(); - var ourFields = members - .OfType() - .SelectMany(x => x.Declaration.Variables.Select(y => (x.Declaration.Type, Var: y))) - .ToArray(); - if (bakedFields.Length != ourFields.Length) - { - throw new InvalidOperationException( - $"Differing number of fields between definitions of {node.Identifier} ({bakedFields.Length} vs " - + $"{ourFields.Length})" - ); - } - - for (var i = 0; i < bakedFields.Length; i++) - { - if ( - bakedFields[i].Type.ToString() != ourFields[i].Type.ToString() - || bakedFields[i].Var.Identifier.ToString() - != ourFields[i].Var.Identifier.ToString() - || bakedFields[i].Var.Initializer?.ToString() - != ourFields[i].Var.Initializer?.ToString() - || bakedFields[i].Var.ArgumentList?.ToString() - != ourFields[i].Var.ArgumentList?.ToString() - ) - { - throw new InvalidOperationException( - $"Field {i} differs between definitions of {node.Identifier}. " - + $"Left: {bakedFields[i]}, right: {ourFields[i]}" - ); - } - } - - return null; // baking erases, but the caller should know that. } private SyntaxNode? Visit( @@ -425,294 +362,55 @@ is not { } members return retNode; } - // If we're not in baking mode... - if (Baked is null) + // If we didn't get an inner node when we're not in baking mode, return now as this is bad. + // Also, we can only add attributes for TSeparated, not the inner TVisiting, so we also return now if we + // want to add an attribute. This is super convoluted I know. The onus is on the caller to add the + // attribute in this case. + if (retNode is not (TSeparated ret and TVisiting)) { - // If we didn't get an inner node when we're not in baking mode, return now as this is bad. - // Also, we can only add attributes for TSeparated, not the inner TVisiting, so we also return now if we - // want to add an attribute. This is super convoluted I know. The onus is on the caller to add the - // attribute in this case. - if (retNode is not (TSeparated ret and TVisiting)) - { - // expected to at least be TVisiting, but we can only add if it's TSeparated. If it's not at least - // TVisiting, it is likely null. In either case this does what we want. - return retNode; - } - - // Add the attribute if this is the node we are visiting. - return ret.AddAttributeLists( - AttributeList(SingletonSeparatedList(GetProfileAttribute(name))) - ); + // expected to at least be TVisiting, but we can only add if it's TSeparated. If it's not at least + // TVisiting, it is likely null. In either case this does what we want. + return retNode; } - Logger?.LogTrace( - "Baking item for \"{}\" with discriminator \"{}\": {}", - Profile.Profile, - discrim, - nodeToAdd + // Add the attribute if this is the node we are visiting. + return ret.AddAttributeLists( + AttributeList(SingletonSeparatedList(GetProfileAttribute(name))) ); - var parent = GetOrRegisterAncestorBakeSet(nodeVisiting); + } - // Have we seen the member before? - if (parent.Children.TryGetValue(discrim, out var baked)) + public override bool ShouldSkipFile(string fileName) + { + if (!fileName.StartsWith("sources/")) { - // Make sure it's the same concrete type - if (baked.Syntax.GetType() != nodeToAdd.GetType()) - { - throw new InvalidOperationException( - $"The existing definition for \"{discrim}\" is a {baked.Syntax.GetType()} whereas this " - + $"definition is a {nodeToAdd.GetType()}. Left: {baked.Syntax}, right: {nodeToAdd}" - ); - } - - // Boldly assume it's the same implementation, modifiers, etc - // TODO ^^^ is this okay? should be fine if we're getting DllImports as inputs. - - // Okay fine here's some extra handling just in case it's a partial: - var bakedNode = (TSeparated)baked.Syntax; - if ( - nodeToAdd is BaseMethodDeclarationSyntax meth - && bakedNode is BaseMethodDeclarationSyntax bakedMeth - && baked.Syntax.Modifiers.Any(SyntaxKind.PartialKeyword) - && nodeToAdd.Modifiers.Any(SyntaxKind.PartialKeyword) - ) - { - var precedence = false; - if (bakedMeth.Body is null && meth.Body is not null) - { - if (bakedMeth.ExpressionBody is not null) - { - throw new InvalidOperationException( - $"The existing definition for \"{discrim}\" provides an expression body whereas this " - + "definition provides a statement body" - ); - } - - // Our definition takes precedence - precedence = true; - } - - if (bakedMeth.ExpressionBody is null && meth.ExpressionBody is not null) - { - if (bakedMeth.Body is not null) - { - throw new InvalidOperationException( - $"The existing definition for \"{discrim}\" provides an expression body whereas this " - + "definition provides a statement body" - ); - } - - if (precedence) - { - throw new InvalidOperationException( - $"This definition for \"{discrim}\" provides both an expression and a statement body." - ); - } - - // Our definition takes precedence - precedence = true; - } - - if (precedence) - { - baked.Syntax = nodeToAdd.AddAttributeLists( - baked - .Syntax.AttributeLists.Select(x => - x.WithAttributes( - SeparatedList( - x.Attributes.Where(y => - y.IsAttribute("Silk.NET.Core.SupportedApiAttribute") - ) - ) - ) - ) - .Where(x => x.Attributes.Count > 0) - .ToArray() - ); - } - } - - // Check that constants and enums have the same value - if ( - (baked.Syntax, nodeToAdd) is - - (EnumMemberDeclarationSyntax lEnum, EnumMemberDeclarationSyntax rEnum) - ) - { - if (lEnum.EqualsValue?.Value.ToString() != rEnum.EqualsValue?.Value.ToString()) - { - Logger?.LogWarning( - "Enum member with discriminator \"{}\" differs between definitions. Left: {}, right: {}", - discrim, - lEnum.EqualsValue?.Value.ToString() ?? "auto-assigned", - rEnum.EqualsValue?.Value.ToString() ?? "auto-assigned" - ); - } - } - else if ( - (baked.Syntax, nodeToAdd) is + return true; + } - (FieldDeclarationSyntax lConst, FieldDeclarationSyntax rConst) + Profile = cfg + .Profiles?.Where(x => + fileName[8..].StartsWith(x.SourceSubdirectory, StringComparison.OrdinalIgnoreCase) ) - { - var isConst = lConst.Modifiers.Any(SyntaxKind.ConstKeyword); - if (isConst != rConst.Modifiers.Any(SyntaxKind.ConstKeyword)) - { - Logger?.LogWarning( - "Const with discriminator \"{}\" isn't const in its redefinition. Left: {}, right: {}", - discrim, - lConst.ToString(), - rConst.ToString() - ); - } - else if ( - isConst - && lConst.Declaration.Variables[0].Initializer?.Value.ToString() - != rConst.Declaration.Variables[0].Initializer?.Value.ToString() - ) - { - Logger?.LogWarning( - "Const value with discriminator \"{}\" differs between definitions. Left: {}, right: {}", - discrim, - lConst.Declaration.Variables[0].Initializer?.Value.ToString(), - rConst.Declaration.Variables[0].Initializer?.Value.ToString() - ); - } - } + .MaxBy(x => x.SourceSubdirectory.Length); + if (Profile is null) + { + return true; } - - // Update the bake set. This adds if we haven't seen the member before, otherwise we just update the - // existing declaration by adding our attribute list. - parent.Children[discrim] = ( - (baked.Syntax ?? nodeToAdd).AddAttributeLists( - AttributeList(SingletonSeparatedList(GetProfileAttribute(name))) - ), - null, - baked.Syntax is null ? parent.Children.Count : baked.Index - ); - - return null; // erase it, but the caller should know to do that anyway. + return false; } - private BakeSet GetOrRegisterAncestorBakeSet(SyntaxNode node) => - node.Parent is null - ? Baked ?? throw new InvalidOperationException("BakeSet not set") - : GetOrRegisterTypeBakeSet(node.Parent); - - private BakeSet GetOrRegisterTypeBakeSet(SyntaxNode node) + public override void OnFileStarted(string fileName) { - var nsPre = node.NamespaceFromSyntaxNode() is { Length: > 0 } ns - ? $"{ns}." - : string.Empty; - var bakeSet = Baked ?? throw new InvalidOperationException("BakeSet not set"); - - // To handle nested types, we go through each ancestor (starting from the top, hence the reverse) and get - // the bake set (or create if needed), and do this for each ancestor until we finally get to the containing - // type of the given node. - foreach ( - var decl in node.AncestorsAndSelf().OfType().Reverse() - ) - { - var discrim = $"{nsPre}{decl.Identifier}"; - if ( - decl is TypeDeclarationSyntax - { - TypeParameterList.Parameters.Count: > 0 and var cnt - } - ) - { - discrim += $"`{cnt}"; - } - nsPre = string.Empty; // only the top-level type shall be prefixed with the namespace - bakeSet = ( - bakeSet!.Children.TryGetValue(discrim, out var baked) - ? bakeSet.Children[discrim] = ( - MergeDecls( - WithProfile(StripBare(decl), Profile), - (BaseTypeDeclarationSyntax)baked.Syntax - ), - baked.Inner ?? new BakeSet(), - baked.Index - ) - : bakeSet.Children[discrim] = ( - WithProfile(StripBare(decl), Profile), - new BakeSet(), - bakeSet.Children.Count - ) - ).Inner; - } + Logger?.LogDebug("Identified profile {} for {}", Profile, fileName); + } - return bakeSet!; - static BaseTypeDeclarationSyntax StripBare(BaseTypeDeclarationSyntax node) => - node switch - { - // TODO do we need to strip more than this for dedupe purposes? - TypeDeclarationSyntax klass => klass.WithMembers(default), - EnumDeclarationSyntax enumeration => enumeration.WithMembers(default), - _ => node - }; - - static BaseTypeDeclarationSyntax MergeDecls( - BaseTypeDeclarationSyntax node1, - BaseTypeDeclarationSyntax node2 - ) + public override void OnFileFinished(string fileName) + { + foreach (var (k, v) in UsingsToAdd) { - if (node1.GetType() != node2.GetType()) - { - throw new ArgumentException( - "Node types differed - the profiles may contain two types with the " - + "same name but with a different datatype (i.e. profile 1 contains a " - + $"{node1.Kind().Humanize()} whereas profile 2 contains a {node2.Kind().Humanize()})" - ); - } - return node1 - .WithModifiers( - TokenList( - node2.Modifiers.Concat(node2.Modifiers).DistinctBy(x => x.ToString()) - ) - ) - .WithBaseList( - node1.BaseList?.WithTypes( - SeparatedList( - node1 - .BaseList.Types.Concat( - node2.BaseList?.Types ?? Enumerable.Empty() - ) - .DistinctBy(x => x.ToString()) - ) - ) - ) - .WithAttributeLists( - List( - node1 - .AttributeLists.SelectMany(x => - x.Attributes.Select(y => - x.WithAttributes(SingletonSeparatedList(y)) - ) - ) - .Concat( - node2.AttributeLists.SelectMany(x => - x.Attributes.Select(y => - x.WithAttributes(SingletonSeparatedList(y)) - ) - ) - ) - .DistinctBy(x => x.ToString()) - ) - ); + aggregatedUsings.TryAdd(k, v); } - - BaseTypeDeclarationSyntax WithProfile( - BaseTypeDeclarationSyntax decl, - ApiProfileDecl? profile - ) => - profile is null - ? decl - : decl.AddAttributeLists( - AttributeList( - SingletonSeparatedList(GetProfileAttribute(decl.Identifier.ToString())) - ) - ); + UsingsToAdd.Clear(); + Profile = null; } } @@ -724,147 +422,15 @@ public Dictionary< > Children { get; } = new(); } - /// - /// A map of baked roots to deduplicated es and their associated discriminants. - /// - private Dictionary _baked = new(); - /// - public override Task AfterScrapeAsync(string key, GeneratedSyntax syntax) + public override Task AfterScrapeAsync(string key, SyntaxContext context) { var cfg = config.Get(key); - var rewriter = new Rewriter(key, versionProviders.Get(key).ToArray()) { Logger = logger }; - var bakery = new Dictionary(); - var baked = new List(); var aggregatedUsings = new Dictionary(); - foreach (var (path, root) in syntax.Files) - { - if (!path.StartsWith("sources/")) - { - continue; - } + var rewriter = new Rewriter(key, versionProviders.Get(key).ToArray(), aggregatedUsings, cfg) { Logger = logger }; - rewriter.Profile = cfg - .Profiles?.Where(x => - path[8..].StartsWith(x.SourceSubdirectory, StringComparison.OrdinalIgnoreCase) - ) - .MaxBy(x => x.SourceSubdirectory.Length); - if (rewriter.Profile is null) - { - continue; - } - - logger.LogDebug("Identified profile {} for {}", rewriter.Profile, path); - if (rewriter.Profile.BakedOutputSubdirectory is not null) - { - var discrim = $"sources/{rewriter.Profile.BakedOutputSubdirectory.Trim('/')}"; - if (!bakery.TryGetValue(discrim, out var bakeSet)) - { - bakeSet = bakery[discrim] = new BakeSet(); - } + context.Rewrite(rewriter); - rewriter.Baked = bakeSet; - baked.Add(path); - } - - syntax.Files[path] = rewriter.Visit(root); - foreach (var (k, v) in rewriter.UsingsToAdd) - { - aggregatedUsings.TryAdd(k, v); - } - rewriter.UsingsToAdd.Clear(); - rewriter.Baked = null; - rewriter.Profile = null; - } - - foreach (var path in baked) - { - syntax.Files.Remove(path); - } - - foreach (var (subdir, bakeSet) in bakery) - { - foreach (var (fqTopLevelType, bakedMember) in bakeSet.Children) - { - var (iden, bakedSyntax) = Bake(bakedMember); - if (iden is null) - { - throw new InvalidOperationException( - "Cannot output an unidentified syntax. Top-level syntax should be type declarations only." - ); - } - - var ns = fqTopLevelType.LastIndexOf('.') is not -1 and var idx - ? fqTopLevelType[..idx] - : null; - syntax.Files[$"{subdir}/{PathForFullyQualified(fqTopLevelType)}"] = - CompilationUnit() - .WithMembers( - ns is null - ? SingletonList(bakedSyntax) - : SingletonList( - FileScopedNamespaceDeclaration( - ModUtils.NamespaceIntoIdentifierName(ns) - ) - .WithMembers(SingletonList(bakedSyntax)) - ) - ) - .WithUsings(ModCSharpSyntaxRewriter.GetUsings(aggregatedUsings, null)); - } - } - - return Task.FromResult(syntax); + return Task.FromResult(context); } - - private static (string? Identifier, MemberDeclarationSyntax Syntax) Bake( - (MemberDeclarationSyntax Syntax, BakeSet? Inner, int Index) member - ) => - member.Syntax switch - { - TypeDeclarationSyntax ty - => ( - ty.Identifier - + ( - ty.TypeParameterList is { Parameters.Count: > 0 and var cnt } - ? $"`{cnt}" - : string.Empty - ), - ty.WithMembers( - List( - ty.Members.Concat( - member - .Inner?.Children.Values.OrderBy(x => x.Index) - .Select(x => Bake(x).Syntax) - ?? Enumerable.Empty() - ) - ) - ) - ), - EnumDeclarationSyntax enumDecl - => ( - enumDecl.Identifier.ToString(), - enumDecl.WithMembers( - SeparatedList( - enumDecl.Members.Concat( - member - .Inner?.Children.Values.OrderBy(x => x.Index) - .Select(x => x.Syntax) - .OfType() - ?? Enumerable.Empty() - ) - ) - ) - ), - DelegateDeclarationSyntax del - => ( - del.Identifier - + ( - del.TypeParameterList is { Parameters.Count: > 0 and var cnt } - ? $"`{cnt}" - : string.Empty - ), - del - ), - var x => (null, x) - }; } diff --git a/sources/SilkTouch/Mods/AddOpaqueStructs.cs b/sources/SilkTouch/Mods/AddOpaqueStructs.cs index 4b6936988a..1170ed6d46 100644 --- a/sources/SilkTouch/Mods/AddOpaqueStructs.cs +++ b/sources/SilkTouch/Mods/AddOpaqueStructs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -55,10 +55,10 @@ public Task> BeforeScrapeAsync(string key, List } /// - public Task AfterScrapeAsync(string key, GeneratedSyntax syntax) + public Task AfterScrapeAsync(string key, SyntaxContext context) { var cfg = _config.Get(key); - var diags = new List(syntax.Diagnostics); + var diags = new List(context.Diagnostics); foreach (var name in cfg.Names ?? Array.Empty()) { var qualified = name.LastIndexOf('.'); @@ -80,7 +80,7 @@ public Task AfterScrapeAsync(string key, GeneratedSyntax syntax continue; } - syntax.Files.Add( + context.AddFile( $"sources/{name[(qualified + 1)..]}.gen.cs", CompilationUnit() .WithMembers( @@ -103,6 +103,6 @@ public Task AfterScrapeAsync(string key, GeneratedSyntax syntax ); } - return Task.FromResult(syntax with { Diagnostics = diags }); + return Task.FromResult(context); } } diff --git a/sources/SilkTouch/Mods/AddVTables.cs b/sources/SilkTouch/Mods/AddVTables.cs index 2da61d9c2b..627df24322 100644 --- a/sources/SilkTouch/Mods/AddVTables.cs +++ b/sources/SilkTouch/Mods/AddVTables.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text.Json.Serialization; using System.Threading.Tasks; +using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -488,6 +489,9 @@ class Rewriter(VTable[] vTables) : ModCSharpSyntaxRewriter private InterfaceDeclarationSyntax? _currentInterface; private VTable[] _vTables = vTables; + public List<(string, SyntaxNode)> NewFiles = []; + + private ClassDeclarationSyntax?[] _currentVTableOutputs = new ClassDeclarationSyntax?[ vTables.Length ]; @@ -1150,10 +1154,75 @@ callConv is not null ) ) ); + + public override void OnFileStarted(string fileName) + { + InterfacePartials.Clear(); + ClassName = null; + } + + public override void OnFileFinished(string fileName) + { + if (InterfacePartials.Count == 0) + { + return; + } + + var ifname = + ClassName is not null + && fileName.Replace(ClassName, $"I{ClassName}") is var ifn + && ifn != fileName + ? ifn + : ModUtils.AddEffectiveSuffix(fileName, "Interfaces"); + var nNamespaces = InterfacePartials.Select(x => x.Namespace).Distinct().Count(); + NewFiles.Add( + ( + ifname, + CompilationUnit() + .WithUsings( + ModCSharpSyntaxRewriter.GetUsings(AggregatedUsings, null) + ) + .WithMembers( + nNamespaces == 1 + ? string.IsNullOrWhiteSpace(InterfacePartials[0].Namespace) + ? List( + InterfacePartials.Select(x => x.Interface) + ) + : SingletonList( + FileScopedNamespaceDeclaration( + ModUtils.NamespaceIntoIdentifierName( + InterfacePartials[0].Namespace + ) + ) + .WithMembers( + List( + InterfacePartials.Select(x => + x.Interface + ) + ) + ) + ) + : List( + InterfacePartials.GroupBy(x => x.Namespace) + .Select(g => + NamespaceDeclaration( + ModUtils.NamespaceIntoIdentifierName(g.Key) + ) + .WithMembers( + List( + g.Select(x => x.Interface) + ) + ) + ) + ) + ) + ) + ); + } } /// - public Task AfterScrapeAsync(string key, GeneratedSyntax syntax) + public Task AfterScrapeAsync(string key, SyntaxContext context) { var cfg = config.Get(key); VTable[]? vTables = null; @@ -1193,83 +1262,26 @@ public Task AfterScrapeAsync(string key, GeneratedSyntax syntax } ); - var newFiles = new List<(string, SyntaxNode)>(rw.FullClassNames.Count); - foreach (var (fname, node) in syntax.Files) - { - if (fname.StartsWith("sources/")) - { - rw.InterfacePartials.Clear(); - rw.ClassName = null; - syntax.Files[fname] = - rw.Visit(node) ?? throw new InvalidOperationException("Visit returned null"); - if (rw.InterfacePartials.Count == 0) - { - continue; - } + rw.NewFiles = new List<(string, SyntaxNode)>(rw.FullClassNames.Count); - var ifname = - rw.ClassName is not null - && fname.Replace(rw.ClassName, $"I{rw.ClassName}") is var ifn - && ifn != fname - ? ifn - : ModUtils.AddEffectiveSuffix(fname, "Interfaces"); - var nNamespaces = rw.InterfacePartials.Select(x => x.Namespace).Distinct().Count(); - newFiles.Add( - ( - ifname, - CompilationUnit() - .WithUsings( - ModCSharpSyntaxRewriter.GetUsings(rw.AggregatedUsings, null) - ) - .WithMembers( - nNamespaces == 1 - ? string.IsNullOrWhiteSpace(rw.InterfacePartials[0].Namespace) - ? List( - rw.InterfacePartials.Select(x => x.Interface) - ) - : SingletonList( - FileScopedNamespaceDeclaration( - ModUtils.NamespaceIntoIdentifierName( - rw.InterfacePartials[0].Namespace - ) - ) - .WithMembers( - List( - rw.InterfacePartials.Select(x => - x.Interface - ) - ) - ) - ) - : List( - rw.InterfacePartials.GroupBy(x => x.Namespace) - .Select(g => - NamespaceDeclaration( - ModUtils.NamespaceIntoIdentifierName(g.Key) - ) - .WithMembers( - List( - g.Select(x => x.Interface) - ) - ) - ) - ) - ) - ) - ); - } - } + context.Rewrite(rw); - foreach (var (fname, node) in newFiles) + foreach (var (fname, node) in rw.NewFiles) { - syntax.Files[fname] = node; + if (node is CompilationUnitSyntax comp) + { + context.AddFile(fname, comp); + } } foreach (var (fname, node) in rw.GetExtraFiles()) { - syntax.Files[fname] = node; + if (node is CompilationUnitSyntax comp) + { + context.AddFile(fname, comp); + } } - return Task.FromResult(syntax); + return Task.FromResult(context); } } diff --git a/sources/SilkTouch/Mods/ChangeNamespace.cs b/sources/SilkTouch/Mods/ChangeNamespace.cs index 93b54bfe6a..c97aba3291 100644 --- a/sources/SilkTouch/Mods/ChangeNamespace.cs +++ b/sources/SilkTouch/Mods/ChangeNamespace.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -140,7 +140,7 @@ public Task AfterScrapeAsync(string key, GeneratedSyntax syntax /// public Task AfterJobAsync(string key) => Task.FromResult(_jobs.Remove(key)); - private class Rewriter : CSharpSyntaxRewriter + private class Rewriter : ContextCSharpSyntaxRewriter { private readonly HashSet _allNamespaces; private readonly IReadOnlyList<(Regex Regex, string Replacement)> _regexes; @@ -172,38 +172,100 @@ CompilationUnitSyntax syntax public override SyntaxNode? VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) { - var oldNs = node.Name.ToString(); + var oldNs = CurrentNamespaceContext?.FullNamespace ?? string.Empty; var newNs = ModUtils.GroupedRegexReplace(_regexes, oldNs); - if (oldNs != newNs && _allNamespaces.Contains(oldNs)) + + if (oldNs == newNs) { - _usingsToAdd.Add(oldNs); + return node; } - return base.VisitNamespaceDeclaration(node) switch + + _usingsToAdd.Add(oldNs); + + var newNames = newNs.Split('.'); + + BaseNamespaceDeclarationSyntax[] namespaces = new BaseNamespaceDeclarationSyntax[newNames.Length]; + + namespaces[namespaces.Length - 1] = CurrentNamespaceContext!.ToCompletedNode()!.WithName(IdentifierName(newNames[namespaces.Length - 1])); + + for (int i = namespaces.Length - 2; i >= 0; i--) { - NamespaceDeclarationSyntax syntax - => syntax.WithName(ModUtils.NamespaceIntoIdentifierName(newNs)), - { } ret => ret, - null => null - }; + namespaces[i] = NamespaceDeclaration(IdentifierName(newNames[i])).WithMembers(List(new MemberDeclarationSyntax[] { namespaces[i + 1] })); + } + + int index = 0; + INamespaceContext currentContext = TopNamespaceContext!; + do + { + if (currentContext.TryGetNamespace(newNames[index], out var ns)) + { + if (index == namespaces.Length - 1) + { + ns!.Merge(namespaces[index], this); + break; + } + + currentContext = ns!; + } + else + { + currentContext.AddNamespace(namespaces[index], this); + break; + } + } + while (true); + + return null; } public override SyntaxNode? VisitFileScopedNamespaceDeclaration( FileScopedNamespaceDeclarationSyntax node ) { - var oldNs = node.Name.ToString(); + var oldNs = CurrentNamespaceContext?.FullNamespace ?? string.Empty; var newNs = ModUtils.GroupedRegexReplace(_regexes, oldNs); - if (oldNs != newNs && _allNamespaces.Contains(oldNs)) + + if (oldNs == newNs) { - _usingsToAdd.Add(oldNs); + return node; } - return base.VisitFileScopedNamespaceDeclaration(node) switch + + _usingsToAdd.Add(oldNs); + + var newNames = newNs.Split('.'); + + BaseNamespaceDeclarationSyntax[] namespaces = new BaseNamespaceDeclarationSyntax[newNames.Length]; + + namespaces[namespaces.Length - 1] = CurrentNamespaceContext!.ToCompletedNode()!.WithName(IdentifierName(newNames[namespaces.Length - 1])); + + for (int i = namespaces.Length - 2; i >= 0; i--) { - FileScopedNamespaceDeclarationSyntax syntax - => syntax.WithName(ModUtils.NamespaceIntoIdentifierName(newNs)), - { } ret => ret, - null => null - }; + namespaces[i] = NamespaceDeclaration(IdentifierName(newNames[i])).WithMembers(List(new MemberDeclarationSyntax[] { namespaces[i + 1] })); + } + + int index = 0; + INamespaceContext currentContext = TopNamespaceContext!; + do + { + if (currentContext.TryGetNamespace(newNames[index], out var ns)) + { + if (index == namespaces.Length - 1) + { + ns!.Merge(namespaces[index], this); + break; + } + + currentContext = ns!; + } + else + { + currentContext.AddNamespace(namespaces[index], this); + break; + } + } + while (true); + + return null; } } } diff --git a/sources/SilkTouch/Mods/Common/IMod.cs b/sources/SilkTouch/Mods/Common/IMod.cs index 22a4d77c3e..2dc695a073 100644 --- a/sources/SilkTouch/Mods/Common/IMod.cs +++ b/sources/SilkTouch/Mods/Common/IMod.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Silk.NET.SilkTouch.Clang; @@ -40,13 +40,13 @@ Task> BeforeScrapeAsync(string key, List rsps) /// opportunity to mutate the syntax tree. /// /// The job name (corresponds to the configuration key for mod configs). - /// The generated output from ClangSharp (or the previous mod). + /// The generated output from ClangSharp (or the previous mod). /// /// The modified syntax nodes to be either passed to the next mod or output from the generator if this is the last /// mod. /// - Task AfterScrapeAsync(string key, GeneratedSyntax syntax) => - Task.FromResult(syntax); + Task AfterScrapeAsync(string key, SyntaxContext context) => + Task.FromResult(context); /// /// Runs before SilkTouch is going to output the MSBuild workspace. The generated documents have already been added, diff --git a/sources/SilkTouch/Mods/Common/Mod.cs b/sources/SilkTouch/Mods/Common/Mod.cs index d8d2c84443..42270b5e10 100644 --- a/sources/SilkTouch/Mods/Common/Mod.cs +++ b/sources/SilkTouch/Mods/Common/Mod.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Silk.NET.SilkTouch.Clang; @@ -55,8 +55,8 @@ public virtual Task> BeforeScrapeAsync(string key, List - public virtual Task AfterScrapeAsync(string key, GeneratedSyntax syntax) => - Task.FromResult(syntax); + public virtual Task AfterScrapeAsync(string key, SyntaxContext context) => + Task.FromResult(context); /// public virtual Task BeforeOutputAsync( diff --git a/sources/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs b/sources/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs index ae77feee34..25812af84e 100644 --- a/sources/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs +++ b/sources/SilkTouch/Mods/Common/ModCSharpSyntaxRewriter.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Silk.NET.SilkTouch.Clang; using Silk.NET.SilkTouch.Mods.Transformation; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -13,7 +14,7 @@ namespace Silk.NET.SilkTouch.Mods; /// containing common functionality for mods. /// public abstract class ModCSharpSyntaxRewriter(bool visitIntoStructuredTrivia = false) - : CSharpSyntaxRewriter(visitIntoStructuredTrivia), + : ContextCSharpSyntaxRewriter(visitIntoStructuredTrivia), ITransformationContext { private ThreadLocal> _usingsToAdd = @@ -144,4 +145,7 @@ protected bool AddUsing(string str) => /// The discriminator string. protected static string Discrim(UsingDirectiveSyntax use) => new(use.WithoutTrivia().ToString().Where(char.IsAsciiLetterOrDigit).ToArray()); + + /// + public override bool ShouldSkipFile(string fileName) => fileName.StartsWith("sources/"); } diff --git a/sources/SilkTouch/SilkTouchGenerator.cs b/sources/SilkTouch/SilkTouchGenerator.cs index 27c345e951..a0aa4196c6 100644 --- a/sources/SilkTouch/SilkTouchGenerator.cs +++ b/sources/SilkTouch/SilkTouchGenerator.cs @@ -254,7 +254,7 @@ var file in await cacheProvider.GetFiles( kvp => CSharpSyntaxTree.ParseText(SourceText.From(kvp.Value)).GetRoot() ); rawBindings.Files.Clear(); // GC ASAP - var bindings = new GeneratedSyntax(syntaxTrees, rawBindings.Diagnostics); + var context = new SyntaxContext(syntaxTrees, rawBindings.Diagnostics); // Mod the bindings // ReSharper disable once LoopCanBeConvertedToQuery @@ -265,7 +265,7 @@ var file in await cacheProvider.GetFiles( mod.GetType().Name, key ); - bindings = await mod.AfterScrapeAsync(key, bindings); + context = await mod.AfterScrapeAsync(key, context); } // Add a license header to files that don't have one @@ -277,8 +277,9 @@ await File.ReadAllLinesAsync(job.DefaultLicenseHeader, ct) .Where(x => x.Length == 0 || x.StartsWith("//")) .Select(x => Comment(x.Trim())) .ToArray(); - foreach (var (file, node) in bindings.Files) + foreach (var (file, comp) in context.Files) { + var node = comp.Node; var shouldAddHeader = !node.GetLeadingTrivia() .Any(x => x.Kind() is SyntaxKind.SingleLineCommentTrivia) @@ -290,7 +291,7 @@ await File.ReadAllLinesAsync(job.DefaultLicenseHeader, ct) ).GetValueOrDefault(); if (shouldAddHeader) { - bindings.Files[file] = node.WithLeadingTrivia( + comp.Node = node.WithLeadingTrivia( defaultLicenseHeaderTrivia.Concat(node.GetLeadingTrivia()) ); } @@ -302,7 +303,7 @@ await File.ReadAllLinesAsync(job.DefaultLicenseHeader, ct) "Bindings generation completed in {} seconds, writing to disk...", sw.Elapsed.TotalSeconds ); - return bindings; + return context.ToGeneratedSyntax(); } ///