From 7d7b880a7c49f0eccf7239c97b6bad4716089a99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 21:41:52 +0000 Subject: [PATCH 1/2] Preserve script compile source locations --- .../Translations/Translations.Designer.cs | 45 ++++++ .../Resources/Translations/Translations.resx | 15 ++ MinecraftClient/Scripting/CSharpRunner.cs | 131 ++++++++++++------ 3 files changed, 149 insertions(+), 42 deletions(-) diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index ab775990..de89b133 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -1780,6 +1780,51 @@ namespace MinecraftClient { return ResourceManager.GetString("bot.script.pm.loaded", resourceCulture); } } + + /// + /// Looks up a localized string similar to Error in {0} at line {1}, column {2}: [{3}] {4}. + /// + internal static string script_compile_error { + get { + return ResourceManager.GetString("script.compile.error", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error in {0}: [{1}] {2}. + /// + internal static string script_compile_error_no_location { + get { + return ResourceManager.GetString("script.compile.error_no_location", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [Script] Compilation failed with error(s):. + /// + internal static string script_compile_failed { + get { + return ResourceManager.GetString("script.compile.failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [Script] Starting compilation for {0}.... + /// + internal static string script_compile_started { + get { + return ResourceManager.GetString("script.compile.started", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [Script] Compilation done with no errors.. + /// + internal static string script_compile_succeeded { + get { + return ResourceManager.GetString("script.compile.succeeded", resourceCulture); + } + } /// /// Looks up a localized string similar to Loaded task: diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index 5494dafc..882faab3 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -693,6 +693,21 @@ cooldown: {6} Script '{0}' loaded. + + Error in {0} at line {1}, column {2}: [{3}] {4} + + + Error in {0}: [{1}] {2} + + + [Script] Compilation failed with error(s): + + + [Script] Starting compilation for {0}... + + + [Script] Compilation done with no errors. + Loaded task: {0} diff --git a/MinecraftClient/Scripting/CSharpRunner.cs b/MinecraftClient/Scripting/CSharpRunner.cs index 668ab336..7f6b35e9 100644 --- a/MinecraftClient/Scripting/CSharpRunner.cs +++ b/MinecraftClient/Scripting/CSharpRunner.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Text; +using Microsoft.CodeAnalysis; using MinecraftClient.Scripting.DynamicRun.Builder; using static MinecraftClient.Settings; @@ -16,6 +17,8 @@ namespace MinecraftClient.Scripting { private static readonly Dictionary CompileCache = new(); + private readonly record struct ScriptSourceLine(int LineNumber, string Text); + /// /// Run the specified C# script file /// @@ -48,12 +51,13 @@ namespace MinecraftClient.Scripting { //Process different sections of the script file bool scriptMain = true; - List script = new(); - List extensions = new(); + List script = new(); + List extensions = new(); List libs = new(); List dlls = new(); - foreach (string line in lines) + for (int i = 0; i < lines.Length; i++) { + string line = lines[i]; if (line.StartsWith("//using")) { libs.Add(line.Replace("//", "").Trim()); @@ -67,43 +71,20 @@ namespace MinecraftClient.Scripting if (line.EndsWith("Extensions")) scriptMain = false; } - else if (scriptMain) - script.Add(line); - else extensions.Add(line); + + if (scriptMain) + script.Add(new(i + 1, line)); + else extensions.Add(new(i + 1, line)); } //Add return statement if missing - if (script.All(line => !line.StartsWith("return ") && !line.Contains(" return "))) - script.Add("return null;"); + bool hasImplicitReturn = script.All(line => !line.Text.StartsWith("return ", StringComparison.Ordinal) + && !line.Text.Contains(" return ", StringComparison.Ordinal)); //Generate a class from the given script - string code = string.Join("\n", new string[] - { - "using System;", - "using System.Collections.Generic;", - "using System.Text.RegularExpressions;", - "using System.Linq;", - "using System.Text;", - "using System.IO;", - "using System.Net;", - "using System.Threading;", - "using MinecraftClient;", - "using MinecraftClient.Scripting;", - "using MinecraftClient.Mapping;", - "using MinecraftClient.Inventory;", - string.Join("\n", libs), - "namespace ScriptLoader {", - "public class Script {", - "public CSharpAPI MCC;", - "public object __run(CSharpAPI __apiHandler, string[] args) {", - "this.MCC = __apiHandler;", - string.Join("\n", script), - "}", - string.Join("\n", extensions), - "}}", - }); + string code = BuildScriptCode(scriptName, script, extensions, libs, hasImplicitReturn); - ConsoleIO.WriteLogLine($"[Script] Starting compilation for {scriptName}..."); + ConsoleIO.WriteLogLine(string.Format(Translations.script_compile_started, scriptName)); //Compile the C# class in memory using all the currently loaded assemblies var result = compiler.Compile(code, Guid.NewGuid().ToString(), dlls); @@ -112,22 +93,17 @@ namespace MinecraftClient.Scripting if (result.Failures is not null) { - ConsoleIO.WriteLogLine("[Script] Compilation failed with error(s):"); + ConsoleIO.WriteLogLine(Translations.script_compile_failed); foreach (var failure in result.Failures) { - // Get the line that contains the error: - - var loc = failure.Location.GetMappedLineSpan(); - var line = code.Split('\n')[loc.StartLinePosition.Line]; - - ConsoleIO.WriteLogLine($"[Script] Error in {scriptName}, on line ({line.Trim()}): [{failure.Id}] {failure.GetMessage()}"); + ConsoleIO.WriteLogLine(FormatCompilationFailure(failure, scriptName)); } throw new CSharpException(CSErrorType.InvalidScript, new InvalidProgramException("Compilation failed due to error(s).")); } - ConsoleIO.WriteLogLine("[Script] Compilation done with no errors."); + ConsoleIO.WriteLogLine(Translations.script_compile_succeeded); //Retrieve compiled assembly assembly = result.Assembly; @@ -151,6 +127,77 @@ namespace MinecraftClient.Scripting else return null; } + private static string BuildScriptCode(string scriptName, IEnumerable script, IEnumerable extensions, IEnumerable libs, bool hasImplicitReturn) + { + StringBuilder codeBuilder = new(); + codeBuilder.AppendLine("using System;"); + codeBuilder.AppendLine("using System.Collections.Generic;"); + codeBuilder.AppendLine("using System.Text.RegularExpressions;"); + codeBuilder.AppendLine("using System.Linq;"); + codeBuilder.AppendLine("using System.Text;"); + codeBuilder.AppendLine("using System.IO;"); + codeBuilder.AppendLine("using System.Net;"); + codeBuilder.AppendLine("using System.Threading;"); + codeBuilder.AppendLine("using MinecraftClient;"); + codeBuilder.AppendLine("using MinecraftClient.Scripting;"); + codeBuilder.AppendLine("using MinecraftClient.Mapping;"); + codeBuilder.AppendLine("using MinecraftClient.Inventory;"); + + foreach (string lib in libs) + codeBuilder.AppendLine(lib); + + codeBuilder.AppendLine("namespace ScriptLoader {"); + codeBuilder.AppendLine("public class Script {"); + codeBuilder.AppendLine("public CSharpAPI MCC;"); + codeBuilder.AppendLine("public object __run(CSharpAPI __apiHandler, string[] args) {"); + codeBuilder.AppendLine("this.MCC = __apiHandler;"); + AppendMappedSection(codeBuilder, scriptName, script); + + if (hasImplicitReturn) + codeBuilder.AppendLine("return null;"); + + codeBuilder.AppendLine("}"); + AppendMappedSection(codeBuilder, scriptName, extensions); + codeBuilder.AppendLine("}}"); + return codeBuilder.ToString(); + } + + private static void AppendMappedSection(StringBuilder codeBuilder, string scriptName, IEnumerable lines) + { + ScriptSourceLine[] sourceLines = lines.ToArray(); + if (sourceLines.Length == 0) + return; + + codeBuilder.AppendLine($@"#line {sourceLines[0].LineNumber} ""{EscapeLineDirectivePath(scriptName)}"""); + + foreach (ScriptSourceLine line in sourceLines) + codeBuilder.AppendLine(line.Text); + + codeBuilder.AppendLine("#line default"); + } + + private static string EscapeLineDirectivePath(string path) + { + return path.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal); + } + + private static string FormatCompilationFailure(Diagnostic failure, string scriptName) + { + if (failure.Location.IsInSource) + { + var location = failure.Location.GetMappedLineSpan(); + string sourcePath = string.IsNullOrWhiteSpace(location.Path) ? scriptName : location.Path; + return string.Format(Translations.script_compile_error, + sourcePath, + location.StartLinePosition.Line + 1, + location.StartLinePosition.Character + 1, + failure.Id, + failure.GetMessage()); + } + + return string.Format(Translations.script_compile_error_no_location, scriptName, failure.Id, failure.GetMessage()); + } + /// /// Quickly calculate a hash for the given script /// From a1b542dad65348a8805f2b4b9e21f5b0c16ad4f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 21:49:12 +0000 Subject: [PATCH 2/2] Tidy script diagnostic helpers --- MinecraftClient/Scripting/CSharpRunner.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/Scripting/CSharpRunner.cs b/MinecraftClient/Scripting/CSharpRunner.cs index 7f6b35e9..724ccb7f 100644 --- a/MinecraftClient/Scripting/CSharpRunner.cs +++ b/MinecraftClient/Scripting/CSharpRunner.cs @@ -72,9 +72,7 @@ namespace MinecraftClient.Scripting scriptMain = false; } - if (scriptMain) - script.Add(new(i + 1, line)); - else extensions.Add(new(i + 1, line)); + (scriptMain ? script : extensions).Add(new(i + 1, line)); } //Add return statement if missing @@ -178,7 +176,7 @@ namespace MinecraftClient.Scripting private static string EscapeLineDirectivePath(string path) { - return path.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal); + return path.Replace("\\", "\\\\").Replace("\"", "\\\""); } private static string FormatCompilationFailure(Diagnostic failure, string scriptName)