diff --git a/VisualRust.Build/RustcParsedMessage.cs b/VisualRust.Build/Message/Human/RustcMessageHuman.cs similarity index 66% rename from VisualRust.Build/RustcParsedMessage.cs rename to VisualRust.Build/Message/Human/RustcMessageHuman.cs index cb2a6cdb..8f3c2488 100644 --- a/VisualRust.Build/RustcParsedMessage.cs +++ b/VisualRust.Build/Message/Human/RustcMessageHuman.cs @@ -1,18 +1,8 @@ -using System; - -namespace VisualRust.Build +namespace VisualRust.Build.Message.Human { - enum RustcParsedMessageType - { - Error, - Warning, - Note, - Help - } - - class RustcParsedMessage + class RustcMessageHuman { - public RustcParsedMessageType Type; + public RustcMessageType Type; public string Message; public string ErrorCode; public string File; @@ -22,7 +12,7 @@ class RustcParsedMessage public int EndColumnNumber; public bool CanExplain; // TODO: currently we don't do anything with this - public RustcParsedMessage(RustcParsedMessageType type, string message, string errorCode, string file, + public RustcMessageHuman(RustcMessageType type, string message, string errorCode, string file, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber) { Type = type; @@ -36,13 +26,13 @@ public RustcParsedMessage(RustcParsedMessageType type, string message, string er CanExplain = false; } - public bool TryMergeWithFollowing(RustcParsedMessage other) + public bool TryMergeWithFollowing(RustcMessageHuman other) { - if ((other.Type == RustcParsedMessageType.Note || other.Type == RustcParsedMessageType.Help) + if ((other.Type == RustcMessageType.Note || other.Type == RustcMessageType.Help) && other.File == this.File && other.LineNumber == this.LineNumber && other.ColumnNumber == this.ColumnNumber && other.EndLineNumber == this.EndLineNumber && other.EndColumnNumber == this.EndColumnNumber) { - var prefix = other.Type == RustcParsedMessageType.Note ? "\nnote: " : "\nhelp: "; + var prefix = other.Type == RustcMessageType.Note ? "\nnote: " : "\nhelp: "; this.Message += prefix + other.Message; return true; } diff --git a/VisualRust.Build/Message/Human/RustcMessageHumanParser.cs b/VisualRust.Build/Message/Human/RustcMessageHumanParser.cs new file mode 100644 index 00000000..e555410a --- /dev/null +++ b/VisualRust.Build/Message/Human/RustcMessageHumanParser.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace VisualRust.Build.Message.Human +{ + static class RustcMessageHumanParser + { + private static readonly Regex defectRegex = new Regex(@"^([^\n:]+):(\d+):(\d+):\s+(\d+):(\d+)\s+(.*)$", RegexOptions.Multiline | RegexOptions.CultureInvariant); + + private static readonly Regex errorCodeRegex = new Regex(@"\[([A-Z]\d\d\d\d)\]$", RegexOptions.CultureInvariant); + + public static IEnumerable Parse(string output) + { + MatchCollection errorMatches = defectRegex.Matches(output); + + RustcMessageHuman previous = null; + foreach (Match match in errorMatches) + { + string remainingMsg = match.Groups[6].Value.Trim(); + Match errorMatch = errorCodeRegex.Match(remainingMsg); + string errorCode = errorMatch.Success ? errorMatch.Groups[1].Value : null; + int line = Int32.Parse(match.Groups[2].Value, NumberStyles.None); + int col = Int32.Parse(match.Groups[3].Value, NumberStyles.None); + int endLine = Int32.Parse(match.Groups[4].Value, NumberStyles.None); + int endCol = Int32.Parse(match.Groups[5].Value, NumberStyles.None); + + if (remainingMsg.StartsWith("warning: ")) + { + string msg = match.Groups[6].Value.Substring(9, match.Groups[6].Value.Length - 9 - (errorCode != null ? 8 : 0)); + if (previous != null) yield return previous; + previous = new RustcMessageHuman(RustcMessageType.Warning, msg, errorCode, match.Groups[1].Value, + line, col, endLine, endCol); + } + else if (remainingMsg.StartsWith("note: ") || remainingMsg.StartsWith("help: ")) + { + if (remainingMsg.StartsWith("help: pass `--explain ") && previous != null) + { + previous.CanExplain = true; + continue; + } + + // NOTE: "note: " and "help: " are both 6 characters long (though hardcoding this is probably still not a very good idea) + string msg = remainingMsg.Substring(6, remainingMsg.Length - 6 - (errorCode != null ? 8 : 0)); + var type = remainingMsg.StartsWith("note: ") ? RustcMessageType.Note : RustcMessageType.Help; + RustcMessageHuman note = new RustcMessageHuman(type, msg, errorCode, match.Groups[1].Value, + line, col, endLine, endCol); + + if (previous != null) + { + // try to merge notes and help messages with a previous message (warning or error where it belongs to), if the span is the same + if (previous.TryMergeWithFollowing(note)) + { + continue; // skip setting new previous, because we successfully merged the new note into the previous message + } + else + { + yield return previous; + } + } + previous = note; + } + else + { + bool startsWithError = remainingMsg.StartsWith("error: "); + string msg = remainingMsg.Substring((startsWithError ? 7 : 0), remainingMsg.Length - (startsWithError ? 7 : 0) - (errorCode != null ? 8 : 0)); + if (previous != null) yield return previous; + previous = new RustcMessageHuman(RustcMessageType.Error, msg, errorCode, match.Groups[1].Value, + line, col, endLine, endCol); + } + } + + if (previous != null) yield return previous; + } + } +} diff --git a/VisualRust.Build/Message/Json/RustcMessageJson.cs b/VisualRust.Build/Message/Json/RustcMessageJson.cs new file mode 100644 index 00000000..e958a4ba --- /dev/null +++ b/VisualRust.Build/Message/Json/RustcMessageJson.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace VisualRust.Build.Message.Json +{ + public class RustcMessageJson + { + public class DiagnosticCode + { + /// + /// The code itself. + /// + public string code { get; set; } + /// + /// An explanation for the code. + /// + public string explanation { get; set; } + } + + public class DiagnosticSpanLine + { + public string text { get; set; } + public int highlight_start { get; set; } + public int highlight_end { get; set; } + } + + public class DiagnosticSpanMacroExpansion + { + /// + /// span where macro was applied to generate this code; note that + /// this may itself derive from a macro (if + /// `span.expansion.is_some()`) + /// + public DiagnosticSpan span { get; set; } + /// + /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") + /// + public String macro_decl_name { get; set; } + /// + /// span where macro was defined (if known) + /// + public DiagnosticSpan def_site_span { get; set; } + } + + public class DiagnosticSpan + { + public string file_name { get; set; } + public int byte_start { get; set; } + public int byte_end { get; set; } + public int line_start { get; set; } + public int line_end { get; set; } + public int column_start { get; set; } + public int column_end { get; set; } + /// + /// Is this a "primary" span -- meaning the point, or one of the points, + /// where the error occurred? + /// + public bool is_primary { get; set; } + /// + /// Source text from the start of line_start to the end of line_end. + /// + public List text { get; set; } + /// + /// Label that should be placed at this location (if any) + /// + public object label { get; set; } + /// + /// If we are suggesting a replacement, this will contain text + /// that should be sliced in atop this span. You may prefer to + /// load the fully rendered version from the parent `Diagnostic`, + /// however. + /// + public String suggested_replacement { get; set; } + /// + /// Macro invocations that created the code at this span, if any. + /// + public DiagnosticSpanMacroExpansion expansion { get; set; } + } + + /// + /// The primary error message. + /// + public string message { get; set; } + public DiagnosticCode code { get; set; } + /// + /// "error: internal compiler error", "error", "warning", "note", "help" + /// + public string level { private get; set; } + public List spans { get; set; } + /// + /// Associated diagnostic messages. + /// + public List children { get; set; } + /// + /// The message as rustc would render it. Currently this is only + /// `Some` for "suggestions", but eventually it will include all + /// snippets. + /// + public object rendered { get; set; } + + public RustcMessageType GetLevelAsEnum() + { + if (level == "error: internal compiler error" || level == "error") + return RustcMessageType.Error; + if (level == "warning") + return RustcMessageType.Warning; + if (level == "note") + return RustcMessageType.Note; + if (level == "help") + return RustcMessageType.Help; + + return RustcMessageType.Error; + } + + public DiagnosticSpan GetPrimarySpan() + { + if (spans == null || spans.Count == 0) + return null; + + return spans.FirstOrDefault(a => a.is_primary); + } + + public String GetErrorCodeAsString() + { + if (code == null) + return string.Empty; + return code.code; + } + } +} diff --git a/VisualRust.Build/Message/Json/RustcMessageJsonParser.cs b/VisualRust.Build/Message/Json/RustcMessageJsonParser.cs new file mode 100644 index 00000000..c0f8950a --- /dev/null +++ b/VisualRust.Build/Message/Json/RustcMessageJsonParser.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; + +namespace VisualRust.Build.Message.Json +{ + public static class RustcMessageJsonParser + { + private static readonly JsonSerializer serializer = new JsonSerializer(); + + public static IEnumerable Parse(String output) + { + var reader = new JsonTextReader(new StringReader(output)) {SupportMultipleContent = true}; + + while (reader.Read()) + yield return serializer.Deserialize(reader); + } + } +} diff --git a/VisualRust.Build/Message/RustcMessageType.cs b/VisualRust.Build/Message/RustcMessageType.cs new file mode 100644 index 00000000..a3a3e7f3 --- /dev/null +++ b/VisualRust.Build/Message/RustcMessageType.cs @@ -0,0 +1,10 @@ +namespace VisualRust.Build.Message +{ + public enum RustcMessageType + { + Error, + Warning, + Note, + Help + } +} diff --git a/VisualRust.Build/Rustc.cs b/VisualRust.Build/Rustc.cs index 826311ef..02d8e537 100644 --- a/VisualRust.Build/Rustc.cs +++ b/VisualRust.Build/Rustc.cs @@ -7,16 +7,15 @@ using System.Threading; using Microsoft.Build.Framework; using System.IO; +using VisualRust.Build.Message; +using VisualRust.Build.Message.Human; +using VisualRust.Build.Message.Json; using VisualRust.Shared; namespace VisualRust.Build { public class Rustc : Microsoft.Build.Utilities.Task { - private static readonly Regex defectRegex = new Regex(@"^([^\n:]+):(\d+):(\d+):\s+(\d+):(\d+)\s+(.*)$", RegexOptions.Multiline | RegexOptions.CultureInvariant); - - private static readonly Regex errorCodeRegex = new Regex(@"\[([A-Z]\d\d\d\d)\]$", RegexOptions.CultureInvariant); - private string[] configFlags = new string[0]; /// /// Sets --cfg option. @@ -154,6 +153,7 @@ public string[] LintsAsForbidden public string CodegenOptions { get; set; } private bool? lto; + /// /// Sets -C lto option. Default value is false. /// @@ -169,10 +169,20 @@ public bool LTO [Required] public string Input { get; set; } + private String installPath; + private Version rustcVersion; + public override bool Execute() { try { + if (!FindRustc()) + return false; + + rustcVersion = GetVersion(); + if (rustcVersion == null) + return false; + return ExecuteInner(); } catch (Exception ex) @@ -182,8 +192,53 @@ public override bool Execute() } } + private bool FindRustc() + { + string target = TargetTriple ?? Shared.Environment.DefaultTarget; + installPath = Shared.Environment.FindInstallPath(target); + if (installPath == null) + { + if (String.Equals(target, Shared.Environment.DefaultTarget, StringComparison.OrdinalIgnoreCase)) + Log.LogError("No Rust installation detected. You can download official Rust installer from https://www.rust-lang.org/downloads.html"); + else + Log.LogError("Could not find a Rust installation that can compile target {0}.", target); + return false; + } + + return true; + } + + private Process CreateProcess(String argumets) + { + var psi = new ProcessStartInfo() + { + CreateNoWindow = true, + FileName = Path.Combine(installPath, "rustc.exe"), + UseShellExecute = false, + WorkingDirectory = WorkingDirectory, + Arguments = argumets, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + var process = new Process(); + process.StartInfo = psi; + + return process; + } + + private Version GetVersion() + { + var process = CreateProcess(" --version"); + process.Start(); + process.WaitForExit(); + return Version.ParseRustcOutput(process.StandardOutput.ReadToEnd()); + } + private bool ExecuteInner() { + var useJsonErrorFormat = rustcVersion.VersionMajor >= 1 && rustcVersion.VersionMinor >= 12; + StringBuilder sb = new StringBuilder(); if (ConfigFlags.Length > 0) sb.AppendFormat(" --cfg {0}", String.Join(",", ConfigFlags)); @@ -219,31 +274,15 @@ private bool ExecuteInner() sb.AppendFormat(" -C lto"); if (CodegenOptions != null) sb.AppendFormat(" -C {0}", CodegenOptions); + if (useJsonErrorFormat) + sb.Append(" --error-format=json"); sb.AppendFormat(" {0}", Input); - string target = TargetTriple ?? Shared.Environment.DefaultTarget; - string installPath = Shared.Environment.FindInstallPath(target); - if(installPath == null) - { - if(String.Equals(target, Shared.Environment.DefaultTarget, StringComparison.OrdinalIgnoreCase)) - Log.LogError("No Rust installation detected. You can download official Rust installer from https://www.rust-lang.org/downloads.html"); - else - Log.LogError("Could not find a Rust installation that can compile target {0}.", target); - return false; - } - var psi = new ProcessStartInfo() - { - CreateNoWindow = true, - FileName = Path.Combine(installPath, "rustc.exe"), - UseShellExecute = false, - WorkingDirectory = WorkingDirectory, - Arguments = sb.ToString(), - RedirectStandardError = true - }; - Log.LogCommandLine(String.Join(" ", psi.FileName, psi.Arguments)); + + var process = CreateProcess(sb.ToString()); + Log.LogCommandLine(String.Join(" ", process.StartInfo.FileName, process.StartInfo.Arguments)); try { - Process process = new Process(); - process.StartInfo = psi; + StringBuilder error = new StringBuilder(); using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) @@ -268,15 +307,32 @@ private bool ExecuteInner() string errorOutput = error.ToString(); // We found some warning or errors in the output, print them out - IEnumerable messages = ParseOutput(errorOutput); - // We found some warning or errors in the output, print them out - foreach (RustcParsedMessage msg in messages) + IEnumerable messagesHuman = null; + IEnumerable messageJson = null; + bool haveAnyMessages = false; + + if (useJsonErrorFormat) { - LogRustcMessage(msg); + messageJson = RustcMessageJsonParser.Parse(errorOutput); + foreach (var msg in messageJson) + { + LogRustcMessage(msg); + haveAnyMessages = true; + } } + else + { + messagesHuman = RustcMessageHumanParser.Parse(errorOutput); + foreach (var msg in messagesHuman) + { + LogRustcMessage(msg); + haveAnyMessages = true; + } + } + // rustc failed but we couldn't sniff anything from stderr // this could be an internal compiler error or a missing main() function (there are probably more errors without spans) - if (process.ExitCode != 0 && !messages.Any()) + if (process.ExitCode != 0 && !haveAnyMessages) { // FIXME: This automatically sets the file to VisualRust.Rust.targets. Is there a way to set no file instead? this.Log.LogError(errorOutput); @@ -291,76 +347,14 @@ private bool ExecuteInner() } } - private IEnumerable ParseOutput(string output) - { - MatchCollection errorMatches = defectRegex.Matches(output); - - RustcParsedMessage previous = null; - foreach (Match match in errorMatches) - { - string remainingMsg = match.Groups[6].Value.Trim(); - Match errorMatch = errorCodeRegex.Match(remainingMsg); - string errorCode = errorMatch.Success ? errorMatch.Groups[1].Value : null; - int line = Int32.Parse(match.Groups[2].Value, System.Globalization.NumberStyles.None); - int col = Int32.Parse(match.Groups[3].Value, System.Globalization.NumberStyles.None); - int endLine = Int32.Parse(match.Groups[4].Value, System.Globalization.NumberStyles.None); - int endCol = Int32.Parse(match.Groups[5].Value, System.Globalization.NumberStyles.None); - - if (remainingMsg.StartsWith("warning: ")) - { - string msg = match.Groups[6].Value.Substring(9, match.Groups[6].Value.Length - 9 - (errorCode != null ? 8 : 0)); - if (previous != null) yield return previous; - previous = new RustcParsedMessage(RustcParsedMessageType.Warning, msg, errorCode, match.Groups[1].Value, - line, col, endLine, endCol); - } - else if (remainingMsg.StartsWith("note: ") || remainingMsg.StartsWith("help: ")) - { - if (remainingMsg.StartsWith("help: pass `--explain ") && previous != null) - { - previous.CanExplain = true; - continue; - } - - // NOTE: "note: " and "help: " are both 6 characters long (though hardcoding this is probably still not a very good idea) - string msg = remainingMsg.Substring(6, remainingMsg.Length - 6 - (errorCode != null ? 8 : 0)); - var type = remainingMsg.StartsWith("note: ") ? RustcParsedMessageType.Note : RustcParsedMessageType.Help; - RustcParsedMessage note = new RustcParsedMessage(type, msg, errorCode, match.Groups[1].Value, - line, col, endLine, endCol); - - if (previous != null) - { - // try to merge notes and help messages with a previous message (warning or error where it belongs to), if the span is the same - if (previous.TryMergeWithFollowing(note)) - { - continue; // skip setting new previous, because we successfully merged the new note into the previous message - } - else - { - yield return previous; - } - } - previous = note; - } - else - { - bool startsWithError = remainingMsg.StartsWith("error: "); - string msg = remainingMsg.Substring((startsWithError ? 7 : 0), remainingMsg.Length - (startsWithError ? 7 : 0) - (errorCode != null ? 8 : 0)); - if (previous != null) yield return previous; - previous = new RustcParsedMessage(RustcParsedMessageType.Error, msg, errorCode, match.Groups[1].Value, - line, col, endLine, endCol); - } - } - - if (previous != null) yield return previous; - } - - private void LogRustcMessage(RustcParsedMessage msg) + + private void LogRustcMessage(RustcMessageHuman msg) { - if (msg.Type == RustcParsedMessageType.Warning) + if (msg.Type == RustcMessageType.Warning) { this.Log.LogWarning(null, msg.ErrorCode, null, msg.File, msg.LineNumber, msg.ColumnNumber, msg.EndLineNumber, msg.EndColumnNumber, msg.Message); } - else if (msg.Type == RustcParsedMessageType.Note) + else if (msg.Type == RustcMessageType.Note) { this.Log.LogWarning(null, msg.ErrorCode, null, msg.File, msg.LineNumber, msg.ColumnNumber, msg.EndLineNumber, msg.EndColumnNumber, "note: " + msg.Message); } @@ -369,5 +363,36 @@ private void LogRustcMessage(RustcParsedMessage msg) this.Log.LogError(null, msg.ErrorCode, null, msg.File, msg.LineNumber, msg.ColumnNumber, msg.EndLineNumber, msg.EndColumnNumber, msg.Message); } } + + private void LogRustcMessage(RustcMessageJson msg) + { + // todo multi span + // todo all other fields + // todo mb help key word is code.explanation + + var type = msg.GetLevelAsEnum(); + var primarySpan = msg.GetPrimarySpan(); + var code = msg.GetErrorCodeAsString(); + + // supress messages like "aborting due to previous error" + if (String.IsNullOrEmpty(code) && primarySpan == null) + return; + + if (type == RustcMessageType.Error) + { + if (primarySpan == null) + Log.LogError(msg.message); + else + Log.LogError(null, code, null, primarySpan.file_name, primarySpan.line_start, primarySpan.column_start, primarySpan.line_end, primarySpan.column_end, msg.message); + } + else + { + if (primarySpan == null) + Log.LogWarning(msg.message); + else + Log.LogWarning(null, code, null, primarySpan.file_name, primarySpan.line_start, primarySpan.column_start, primarySpan.line_end, primarySpan.column_end, msg.message); + } + + } } } diff --git a/VisualRust.Build/Version.cs b/VisualRust.Build/Version.cs new file mode 100644 index 00000000..bbea8e80 --- /dev/null +++ b/VisualRust.Build/Version.cs @@ -0,0 +1,38 @@ +using System; + +namespace VisualRust.Build +{ + class Version + { + public UInt32 VersionMajor { get; private set; } + public UInt32 VersionMinor { get; private set; } + public UInt32 VersionPatch { get; private set; } + public string VersionVariant { get; private set; } + + public static Version ParseRustcOutput(String output) + { + var outputParts = output.Split(' '); + if (outputParts.Length != 4) + return null; + + var releaseParts = outputParts[1].Split('.', '-'); + if (releaseParts.Length != 4) + return null; + + var result = new Version(); + try + { + result.VersionMajor = Convert.ToUInt32(releaseParts[0]); + result.VersionMinor = Convert.ToUInt32(releaseParts[1]); + result.VersionPatch = Convert.ToUInt32(releaseParts[2]); + result.VersionVariant = releaseParts[3].Trim().ToLowerInvariant(); + } + catch (Exception ex) + { + return null; + } + + return result; + } + } +} diff --git a/VisualRust.Build/VisualRust.Build.csproj b/VisualRust.Build/VisualRust.Build.csproj index 5a80b610..da51a021 100644 --- a/VisualRust.Build/VisualRust.Build.csproj +++ b/VisualRust.Build/VisualRust.Build.csproj @@ -40,14 +40,25 @@ + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + - + + + PreserveNewest + Designer + + + + diff --git a/VisualRust.Build/packages.config b/VisualRust.Build/packages.config new file mode 100644 index 00000000..e1fae9c6 --- /dev/null +++ b/VisualRust.Build/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/VisualRust.Setup/VisualRust.wxs b/VisualRust.Setup/VisualRust.wxs index 32f41dcf..98bfaec8 100644 --- a/VisualRust.Setup/VisualRust.wxs +++ b/VisualRust.Setup/VisualRust.wxs @@ -103,6 +103,9 @@ + + + \ No newline at end of file diff --git a/VisualRust.Test/Build/RustcMessageJsonParserTest.cs b/VisualRust.Test/Build/RustcMessageJsonParserTest.cs new file mode 100644 index 00000000..9cb60c90 --- /dev/null +++ b/VisualRust.Test/Build/RustcMessageJsonParserTest.cs @@ -0,0 +1,66 @@ +using System.Linq; +using NUnit.Framework; +using VisualRust.Build.Message; +using VisualRust.Build.Message.Json; + +namespace VisualRust.Test.Build +{ + class RustcMessageJsonParserTest + { + [Test] + public void WithoutError() + { + const string output = ""; + var messages = RustcMessageJsonParser.Parse(output).ToList(); + + Assert.AreEqual(0, messages.Count); + } + + [Test] + public void FileNotFound() + { + const string output = "{\"message\":\"couldn't read \\\"main2.rs\\\": file" + + " not found. (os error 2)\",\"code\":null,\"level\":\"error" + + "\",\"spans\":[],\"children\":[],\"rendered\":null}\n"; + var messages = RustcMessageJsonParser.Parse(output).ToList(); + + Assert.AreEqual(1, messages.Count); + Assert.AreEqual(RustcMessageType.Error, messages[0].GetLevelAsEnum()); + } + + [Test] + public void NoMain() + { + const string output = "{\"message\":\"main function not found\",\"code\":null" + + ",\"level\":\"error\",\"spans\":[],\"children\":[],\"rendered\":null" + + "}\n{\"message\":\"aborting due to previous error\",\"code\":null,\"level" + + "\":\"error\",\"spans\":[],\"children\":[],\"rendered\":null}\n"; + var messages = RustcMessageJsonParser.Parse(output).ToList(); + + Assert.AreEqual(2, messages.Count); + } + + [Test] + public void WithWarning() + { + const string output = "{\"message\":\"unused variable: `x`, #[warn" + + "(unused_variables)] on by default\",\"code\":null,\"level\":\"war" + + "ning\",\"spans\":[{\"file_name\":\"main.rs\",\"byte_start\":498,\"by" + + "te_end\":499,\"line_start\":12,\"line_end\":12,\"column_start\":9,\"c" + + "olumn_end\":10,\"is_primary\":true,\"text\":[{\"text\":\" let x " + + "= 42;\\r\",\"highlight_start\":9,\"highlight_end\":10}],\"label\":nu" + + "ll,\"suggested_replacement\":null,\"expansion\":null}],\"chil" + + "dren\":[],\"rendered\":null}\n"; + var messages = RustcMessageJsonParser.Parse(output).ToList(); + + Assert.AreEqual(1, messages.Count); + Assert.AreEqual(RustcMessageType.Warning, messages[0].GetLevelAsEnum()); + + var span = messages[0].GetPrimarySpan(); + Assert.NotNull(span); + Assert.True(span.is_primary); + + + } + } +} diff --git a/VisualRust.Test/VisualRust.Test.csproj b/VisualRust.Test/VisualRust.Test.csproj index b5d33586..03f1e44a 100644 --- a/VisualRust.Test/VisualRust.Test.csproj +++ b/VisualRust.Test/VisualRust.Test.csproj @@ -64,6 +64,7 @@ + @@ -104,10 +105,16 @@ - + + Designer + + + {9cf556ab-76fe-4c3d-ad0a-b64b3b9989b4} + VisualRust.Build + {2594db0d-03ff-40ea-8d27-9930e5d3ff83} VisualRust.Cargo @@ -117,7 +124,9 @@ VisualRust.Project - + + +