From 0e0b948c1522b8719742592c20237da08ac7a67f Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Mon, 22 Dec 2025 17:46:39 -0600 Subject: [PATCH 1/7] Fixed #27 Usings under namespace aren't included --- src/PluginMerge/Merge/FileHandler.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/PluginMerge/Merge/FileHandler.cs b/src/PluginMerge/Merge/FileHandler.cs index 74ec5d9..aa4e625 100644 --- a/src/PluginMerge/Merge/FileHandler.cs +++ b/src/PluginMerge/Merge/FileHandler.cs @@ -97,7 +97,7 @@ public async Task ProcessFile(PlatformSettings settings, CSharpParseOptions opti return; } - await Task.WhenAll(ProcessUsings(settings, root), ProcessNamespace(root)).ConfigureAwait(false); + await Task.WhenAll(ProcessUsings(settings, root), ProcessNamespace(settings, root)).ConfigureAwait(false); } private Task ProcessComments(CompilationUnitSyntax root) @@ -173,7 +173,13 @@ private void ProcessFrameworkComments(string comment) private Task ProcessUsings(PlatformSettings settings, CompilationUnitSyntax root) { _logger.LogDebug("Start processing usings file at path: {Path}", RegionName); - foreach (UsingDirectiveSyntax @using in root.Usings) + AddUsings(settings, root.Usings); + return Task.CompletedTask; + } + + private void AddUsings(PlatformSettings settings, SyntaxList usings) + { + foreach (UsingDirectiveSyntax @using in usings) { string name = @using.Name.ToString(); if (!name.Equals(settings.Namespace)) @@ -181,15 +187,15 @@ private Task ProcessUsings(PlatformSettings settings, CompilationUnitSyntax root UsingStatements.Add(@using); } } - - return Task.CompletedTask; } - - private Task ProcessNamespace(CompilationUnitSyntax root) + + private Task ProcessNamespace(PlatformSettings settings, CompilationUnitSyntax root) { _logger.LogDebug("Start processing namespace file at path: {Path}", RegionName); foreach (BaseNamespaceDeclarationSyntax @namespace in root.ChildNodes().OfType()) { + AddUsings(settings, @namespace.Usings); + foreach (BaseTypeDeclarationSyntax node in @namespace.ChildNodes().OfType()) { FileSettings typeSettings = Settings; From 511acd65d7093ed5fd95f36ac39b4c31d304236d Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Mon, 22 Dec 2025 18:07:29 -0600 Subject: [PATCH 2/7] Support File Scoped Namespace in FileCreator --- src/PluginMerge/Creator/FileCreator.cs | 26 ++++++++++++++------------ src/PluginMerge/Writer/CodeWriter.cs | 7 ++++++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/PluginMerge/Creator/FileCreator.cs b/src/PluginMerge/Creator/FileCreator.cs index 3a6fc1b..3a5bebf 100644 --- a/src/PluginMerge/Creator/FileCreator.cs +++ b/src/PluginMerge/Creator/FileCreator.cs @@ -70,13 +70,14 @@ public bool Create() FilterFiles(_plugin.PluginData); _writer = new CodeWriter(_plugin.PluginData, _settings.Merge); + bool hasExtensionMethods = _extensionTypes.Count != 0; WriteRequires(); WriteReferences(); WriteRequiredPreprocessorDirectives(); WriteDefines(); WriteUsings(); - WriteNamespace(); + WriteNamespace(!hasExtensionMethods); if (IsMergeFrameworkMode) _writer.WriteFramework(); StartPluginClass(); WritePluginFiles(); @@ -84,8 +85,11 @@ public bool Create() EndPluginClass(); if (IsMergeFrameworkMode) WriteDataFiles(); WriteFrameworks(); - EndNamespace(); - WriteExtensionMethods(); + if (hasExtensionMethods) + { + EndNamespace(); + WriteExtensionMethods(); + } WriteErrorPreprocessorMessages(); return true; } @@ -220,12 +224,15 @@ private void WriteUsings() /// /// Writes namespace to the code writer /// - private void WriteNamespace() + private void WriteNamespace(bool fileScoped) { _writer.WriteComment($"{_settings.Merge.PluginName} created with PluginMerge v({typeof(Program).Assembly.GetName().Version}) by MJSU @ https://github.com/dassjosh/Plugin.Merge"); - _writer.WriteNamespace(_settings.PlatformSettings.Namespace); + _writer.WriteNamespace(_settings.PlatformSettings.Namespace, fileScoped); _writer.WriteLine(); - _writer.WriteStartBracket(); + if (!fileScoped) + { + _writer.WriteStartBracket(); + } } /// @@ -234,7 +241,7 @@ private void WriteNamespace() private void WriteExtensionNamespace() { _writer.WriteLine(); - _writer.WriteNamespace(GetExtensionNamespace()); + _writer.WriteNamespace(GetExtensionNamespace(), false); _writer.WriteLine(); _writer.WriteStartBracket(); if (_settings.Merge.CreatorMode != CreatorMode.MergeFramework) @@ -371,11 +378,6 @@ private void WriteFrameworks() private void WriteExtensionMethods() { - if (_extensionTypes.Count == 0) - { - return; - } - bool isFramework = IsFrameworkMode || IsMergeFrameworkMode; WriteExtensionNamespace(); diff --git a/src/PluginMerge/Writer/CodeWriter.cs b/src/PluginMerge/Writer/CodeWriter.cs index 3320c3e..b701770 100644 --- a/src/PluginMerge/Writer/CodeWriter.cs +++ b/src/PluginMerge/Writer/CodeWriter.cs @@ -137,10 +137,15 @@ public void WriteUsingAlias(string type, string typeNamespace) /// Writes namespace to the code /// /// - public void WriteNamespace(string @namespace) + public void WriteNamespace(string @namespace, bool isFileScoped) { _writer.Append("namespace "); _writer.Append(@namespace); + if (isFileScoped) + { + _writer.Append(';'); + _writer.AppendLine(); + } } /// From 40c2bcd45ba7341cff9edca17ca4fe992e5378b4 Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Mon, 22 Dec 2025 18:19:42 -0600 Subject: [PATCH 3/7] Automatically detect extension methods. Removed //Define:ExtensionMethods comment processing as it's no longer needed. --- src/PluginMerge/Constants.cs | 5 ----- src/PluginMerge/Creator/FileCreator.cs | 9 --------- src/PluginMerge/Merge/FileHandler.cs | 11 +++++++---- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/PluginMerge/Constants.cs b/src/PluginMerge/Constants.cs index 10bb5a1..e19d10d 100644 --- a/src/PluginMerge/Constants.cs +++ b/src/PluginMerge/Constants.cs @@ -21,11 +21,6 @@ public static class Definitions /// Files with this comment will be ignored /// public const string ExcludeFile = "//Define:ExcludeFile"; - - /// - /// Files with this comment will be added as extension methods - /// - public const string ExtensionFile = "//Define:ExtensionMethods"; /// /// Files with this comment will be ordered based on the tag diff --git a/src/PluginMerge/Creator/FileCreator.cs b/src/PluginMerge/Creator/FileCreator.cs index 3a5bebf..f1c94ba 100644 --- a/src/PluginMerge/Creator/FileCreator.cs +++ b/src/PluginMerge/Creator/FileCreator.cs @@ -378,19 +378,10 @@ private void WriteFrameworks() private void WriteExtensionMethods() { - bool isFramework = IsFrameworkMode || IsMergeFrameworkMode; - WriteExtensionNamespace(); foreach (IFileType type in _extensionTypes) { _logger.LogDebug("Writing extension type: {Path}", type.TypeName); - - if (isFramework) - { - _writer.WriteLine(); - _writer.WriteDefinition(Constants.Definitions.ExtensionFile); - } - type.Write(_writer); } EndNamespace(); diff --git a/src/PluginMerge/Merge/FileHandler.cs b/src/PluginMerge/Merge/FileHandler.cs index aa4e625..e15af96 100644 --- a/src/PluginMerge/Merge/FileHandler.cs +++ b/src/PluginMerge/Merge/FileHandler.cs @@ -199,13 +199,16 @@ private Task ProcessNamespace(PlatformSettings settings, CompilationUnitSyntax r foreach (BaseTypeDeclarationSyntax node in @namespace.ChildNodes().OfType()) { FileSettings typeSettings = Settings; - foreach (SyntaxTrivia trivia in node.DescendantTrivia()) + if (node is ClassDeclarationSyntax @class && @class.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) { - if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) + foreach (MethodDeclarationSyntax method in @class.ChildNodes().OfType()) { - if (trivia.ToString() == Constants.Definitions.ExtensionFile) + if (method.ParameterList.Parameters.Count != 0) { - typeSettings |= FileSettings.Extension; + if (method.ParameterList.Parameters[0].Modifiers.Any(m => m.IsKind(SyntaxKind.ThisKeyword))) + { + typeSettings |= FileSettings.Extension; + } } } } From 4b444f79e6383a45d48df661058c6a1f19e7b138 Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Tue, 23 Dec 2025 08:20:46 -0600 Subject: [PATCH 4/7] Bump C# version to 14 --- src/PluginMerge/PluginMerge.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PluginMerge/PluginMerge.csproj b/src/PluginMerge/PluginMerge.csproj index d93b6c2..030fb0a 100644 --- a/src/PluginMerge/PluginMerge.csproj +++ b/src/PluginMerge/PluginMerge.csproj @@ -7,7 +7,7 @@ ./bin/nupkg $(Version) $(Version) - 10 + 14 MJSU enable $(Version) From 52e07a9b8b096d22f994bd62ccdf9fcc0e23004e Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Tue, 23 Dec 2025 09:17:35 -0600 Subject: [PATCH 5/7] Improved using statement processing to properly handle namespace usings. --- src/PluginMerge/Creator/FileCreator.cs | 87 ++++++++++++++++++++------ src/PluginMerge/Writer/CodeWriter.cs | 1 + 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/PluginMerge/Creator/FileCreator.cs b/src/PluginMerge/Creator/FileCreator.cs index f1c94ba..4081fe5 100644 --- a/src/PluginMerge/Creator/FileCreator.cs +++ b/src/PluginMerge/Creator/FileCreator.cs @@ -1,3 +1,5 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; + namespace PluginMerge.Creator; /// @@ -78,6 +80,7 @@ public bool Create() WriteDefines(); WriteUsings(); WriteNamespace(!hasExtensionMethods); + WriteNamespaceUsings(); if (IsMergeFrameworkMode) _writer.WriteFramework(); StartPluginClass(); WritePluginFiles(); @@ -192,34 +195,40 @@ private void WriteDefines() /// private void WriteUsings() { - List extensionNameSpaces = _files - .SelectMany(f => f.FileTypes + string[] extensionNameSpaces = _files.SelectMany(f => f.FileTypes .Where(f => f.IsExtensionMethods()) .Select(f => f.TypeNamespace)) - .ToList(); - - var uniqueUsings = _files - .SelectMany(f => f.UsingStatements.Select(u => new - { - File = f, - Using = u, - UsingText = u.ToString(), - UsingName = u.Name.ToString() - })) - .DistinctBy(u => u.UsingText) - .Where(u => !_settings.Merge.IgnoreNameSpaces.Any(u.UsingName.StartsWith) && !extensionNameSpaces.Contains(u.UsingText)) - .OrderBy(u => u.UsingText) - .ToArray(); + .ToArray(); - _writer.WriteUsings(uniqueUsings.Where(u => u.Using.Alias == null && u.Using.StaticKeyword == default).Select(u => u.UsingText)); - _writer.WriteUsings(uniqueUsings.Where(u => u.Using.StaticKeyword != default).Select(u => u.UsingText)); - _writer.WriteUsings(uniqueUsings.Where(u => u.Using.Alias is not null).Select(u => u.UsingText)); + WriteUsings(_files.SelectMany(f => f.UsingStatements.Select(u => new UsingStatementData(u))).Where(u => !u.IsNamespace), extensionNameSpaces); if (_extensionTypes.Count != 0) { _writer.WriteUsing(GetExtensionNamespace()); + _writer.WriteLine(); } } + + /// + /// writes usings to the code writer + /// + private void WriteNamespaceUsings() + { + WriteUsings(_files.SelectMany(f => f.UsingStatements.Select(u => new UsingStatementData(u))).Where(u => u.IsNamespace), []); + } + + private void WriteUsings(IEnumerable usings, string[] extensionNameSpaces) + { + UsingStatementData[] uniqueUsings = usings + .DistinctBy(u => u.UsingText) + .Where(u => !_settings.Merge.IgnoreNameSpaces.Any(u.UsingName.StartsWith) && !extensionNameSpaces.Contains(u.UsingText)) + .OrderBy(u => u.UsingText) + .ToArray(); + + _writer.WriteUsings(uniqueUsings.Where(u => u.IsDefault).Select(u => u.UsingText)); + _writer.WriteUsings(uniqueUsings.Where(u => u.IsStatic).Select(u => u.UsingText)); + _writer.WriteUsings(uniqueUsings.Where(u => u.IsAlias).Select(u => u.UsingText)); + } /// /// Writes namespace to the code writer @@ -400,4 +409,44 @@ private string GetExtensionNamespace() { return $"{_settings.PlatformSettings.Namespace}.{_settings.Merge.PluginName}Extensions"; } + + + private sealed class UsingStatementData + { + public readonly string UsingText; + public readonly string UsingName; + private readonly UsingType _type; + + public bool IsDefault => !IsStatic && !IsAlias; + public bool IsStatic => _type.HasFlag(UsingType.Static); + public bool IsAlias => _type.HasFlag(UsingType.Alias); + public bool IsNamespace => _type.HasFlag(UsingType.Namespace); + + public UsingStatementData(UsingDirectiveSyntax @using) + { + UsingText = @using.ToString(); + UsingName = @using.Name.ToString(); + if (@using.StaticKeyword != default) + { + _type |= UsingType.Static; + } + if (@using.Alias is not null) + { + _type |= UsingType.Alias; + } + if(@using.Parent is BaseNamespaceDeclarationSyntax) + { + _type |= UsingType.Namespace; + } + } + } + + [Flags] + private enum UsingType + { + Default = 0, + Static = 1 << 0, + Alias = 1 << 1, + Namespace = 1 << 2 + } } \ No newline at end of file diff --git a/src/PluginMerge/Writer/CodeWriter.cs b/src/PluginMerge/Writer/CodeWriter.cs index b701770..0983b34 100644 --- a/src/PluginMerge/Writer/CodeWriter.cs +++ b/src/PluginMerge/Writer/CodeWriter.cs @@ -106,6 +106,7 @@ public void WriteUsings(IEnumerable usings) foreach (string @using in usings) { didWrite = true; + WriteIndent(); _writer.AppendLine(@using); } From 33250143d274cf1c14d8364db2b2be638a05eee8 Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Tue, 23 Dec 2025 09:24:45 -0600 Subject: [PATCH 6/7] Update README --- README.md | 63 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 860d5aa..25d7505 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Plugin Merge is a .net 6+ CLI tool that allows merging multiple .cs files into a [Discord Chat](https://github.com/dassjosh/Plugin.DiscordChat) [Discord Core](https://github.com/dassjosh/Plugin.DiscordCore) [Discord Players](https://github.com/dassjosh/Plugin.DiscordPlayers) +[Discord Roles](https://github.com/dassjosh/Plugin.DiscordRoles) ## Installation `dotnet tool install --global MJSU.Plugin.Merge` from the shell/command line. @@ -64,9 +65,9 @@ Place your config file a directory near your plugin .cs files. Update the config paths to point to the input and output paths you would like to use. The configuration supports relative pathing and all paths use the configuration files directory as it's staring point. -Once your configuration file is setup it's time to merge the files together. +Once your configuration file is setup it's time to merge the files. You can run the merge by typing `plugin.merge -m -p ./merge.yml`. -This will merge all the .cs files together and create a final file in the output paths specified. +This will merge all the .cs files and create a final file in the output paths specified. You can also enable compilation to compile your plugin to check for any issues before loading it onto your server. To enable compilation add the `-c` argument while merging Ex: `plugin.merge -m -c -p ./merge.yml` @@ -98,37 +99,39 @@ There are 3 types of merge options when using Plugin Merge. ### YAML Configuration File ```yaml -# What platform to write the code file for (Oxide, uMod) +# What platform to write the code file for (Oxide) Platform: Oxide Merge Settings: -# Outputted plugin name + # Outputted plugin name Plugin Name: MyPluginName - # Outputted plugin base class - Plugin Base Class: CovalencePlugin # Which type of file to output (Plugin, Framework, or MergeFramework) Creator Mode: Plugin + # Overrides the default namespace + Namespace Override: '' # Paths to use when reading in source code relative to the merge config Plugin Input Paths: - - ./ + - ./ # Paths to use when writing the plugin file relative to the merge config Plugin Output Paths: - - ./build + - ./build # Oxide //References: definitions Reference Definitions: [] + # Oxide //Requires: definitions + Requires Definitions: [] # #define definitions Define Definitions: - - DEBUG + - DEBUG # Paths to be ignored when searching for source code relative to merge config Ignore Paths: - - ./IgnoreThisPath + - ./IgnoreThisPath # Files to be ignored when searching for source code relative to merge config Ignore Files: - - ./IgnoreThisFile.cs + - ./IgnoreThisFile.cs # Namespaces to ignore when processing output file Ignore Namespaces: - - IgnoreThisNameSpace + - IgnoreThisNameSpace Code Style: - # Character to use for code indents + # Character to use for code indents Indent Character: ' ' # The amount of characters to use when indenting once Indent Char Amount: 4 @@ -140,18 +143,26 @@ Merge Settings: Write The Relative File Path In Region: true # Adds the code file path in a region Keep Code Comments: true +Preprocessor Directive Settings: + # Preprocessor Directives that are required to build the plugin + Preprocessor Directives: + - # The Directive That Is Required By The Plugin + Directive: OXIDE + # The Compiler Error Message Show When The Directive Is Missing + Error Message: This plugin requires OXIDE + # If This Directive Is Enabled + Enabled: false Compile Settings: - AssemblyPaths: - - ./Assemblies + Assembly Paths: + - ./Assemblies # Ignores the following paths relative to the merge config Ignore Paths: - - ./Assemblies/x86 - - ./Assemblies/x64 + - ./Assemblies/x86 + - ./Assemblies/x64 # Ignores the following files relative to the merge config Ignore Files: - - ./Assemblies/Newtonsoft.Json.dll + - ./Assemblies/Newtonsoft.Json.dll Compile Log Level (Hidden, Info, Warning, Error): Error - ``` ### JSON Configuration File @@ -160,8 +171,8 @@ Compile Settings: "Platform": "Oxide", "Merge Settings": { "Plugin Name": "MyPluginName", - "Plugin Base Class": "CovalencePlugin", "Creator Mode": "Plugin", + "Namespace Override": "", "Plugin Input Paths": [ "./" ], @@ -169,6 +180,7 @@ Compile Settings: "./build" ], "Reference Definitions": [], + "Requires Definitions": [], "Define Definitions": [ "DEBUG" ], @@ -187,9 +199,18 @@ Compile Settings: "Indent Multiplier": 1, "New Line String": "\r\n", "Write The Relative File Path In Region": true, - "Keep Comments": true + "Keep Code Comments": true } }, + "Preprocessor Directive Settings": { + "Preprocessor Directives": [ + { + "Directive": "OXIDE", + "Error Message": "This plugin requires OXIDE", + "Enabled": false + } + ] + }, "Compile Settings": { "Assembly Paths": [ "./Assemblies" From 192eb34ed11bba8d8176d91c2b15c34f0e913c05 Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Tue, 23 Dec 2025 09:28:11 -0600 Subject: [PATCH 7/7] Bump CI dotnet version to 10.x --- .github/workflows/ci.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b37fdd..3653ac9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Build run: dotnet build --configuration Release working-directory: src \ No newline at end of file diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 2148aa6..30b39a4 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -16,7 +16,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Build run: dotnet build --configuration Release /p:Version=${VERSION} working-directory: src diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6620086..303aec4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Build run: dotnet build --configuration Release /p:Version=${VERSION} working-directory: src