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();
}
///