From d6bf6dcf3413840e562da3a6b3f1efac8849afda Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 19 Sep 2024 16:34:58 -0400 Subject: [PATCH 1/4] Add status checks to the test runner Contributes to #1125 Replace the existing logging code with the utility class that writes to the console, and adds a status check record. Log failures as status checks, but status is only to the output console. --- .github/workflows/renumber-sections.yaml | 1 + .github/workflows/test-examples.yaml | 5 +++ .github/workflows/word-converter.yaml | 1 + .gitignore | 3 ++ tools/ExampleTester/ExampleTester.csproj | 1 + tools/ExampleTester/GeneratedExample.cs | 35 +++++++++-------- tools/ExampleTester/Program.cs | 17 +++++++-- tools/MarkdownConverter/Spec/Reporter.cs | 4 +- .../ReferenceUpdateProcessor.cs | 2 +- .../TocSectionNumberBuilder.cs | 2 +- tools/Utilities/StatusCheckLogger.cs | 38 ++++++++++++++----- tools/tools.sln | 3 ++ 12 files changed, 78 insertions(+), 34 deletions(-) diff --git a/.github/workflows/renumber-sections.yaml b/.github/workflows/renumber-sections.yaml index 021b34881..13f4ecba7 100644 --- a/.github/workflows/renumber-sections.yaml +++ b/.github/workflows/renumber-sections.yaml @@ -16,6 +16,7 @@ jobs: runs-on: ubuntu-latest permissions: checks: write + pull-requests: write env: DOTNET_NOLOGO: true GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-examples.yaml b/.github/workflows/test-examples.yaml index 8ecc583b3..727e996fb 100644 --- a/.github/workflows/test-examples.yaml +++ b/.github/workflows/test-examples.yaml @@ -16,8 +16,13 @@ on: jobs: test-extraction-and-runner: runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write env: DOTNET_NOLOGO: true + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} steps: - name: Check out our repo diff --git a/.github/workflows/word-converter.yaml b/.github/workflows/word-converter.yaml index f70fad9a9..696f998fd 100644 --- a/.github/workflows/word-converter.yaml +++ b/.github/workflows/word-converter.yaml @@ -16,6 +16,7 @@ jobs: runs-on: ubuntu-latest permissions: checks: write + pull-requests: write env: DOTNET_NOLOGO: true GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index a4e559bb3..17843972a 100644 --- a/.gitignore +++ b/.gitignore @@ -360,3 +360,6 @@ test-grammar/ # don't checkin jar files: *.jar + +# don't checkin launchSettings: +**/launchSettings.json diff --git a/tools/ExampleTester/ExampleTester.csproj b/tools/ExampleTester/ExampleTester.csproj index 507659351..582e6ac54 100644 --- a/tools/ExampleTester/ExampleTester.csproj +++ b/tools/ExampleTester/ExampleTester.csproj @@ -16,6 +16,7 @@ + diff --git a/tools/ExampleTester/GeneratedExample.cs b/tools/ExampleTester/GeneratedExample.cs index cafaec18c..eb8af52fd 100644 --- a/tools/ExampleTester/GeneratedExample.cs +++ b/tools/ExampleTester/GeneratedExample.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using System.Reflection; using System.Text; +using Utilities; namespace ExampleTester; @@ -33,9 +34,9 @@ private static GeneratedExample Load(string directory) return new GeneratedExample(directory); } - internal async Task Test(TesterConfiguration configuration) + internal async Task Test(TesterConfiguration configuration, StatusCheckLogger logger) { - var outputLines = new List { $"Testing {Metadata.Name} from {Metadata.Source}" }; + logger.ConsoleOnlyLog(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $"Testing {Metadata.Name} from {Metadata.Source}", "ExampleTester")); // Explicitly do a release build, to avoid implicitly defining DEBUG. var properties = new Dictionary { { "Configuration", "Release" } }; @@ -52,21 +53,16 @@ internal async Task Test(TesterConfiguration configuration) } bool ret = true; - ret &= ValidateDiagnostics("errors", DiagnosticSeverity.Error, Metadata.ExpectedErrors); - ret &= ValidateDiagnostics("warnings", DiagnosticSeverity.Warning, Metadata.ExpectedWarnings, Metadata.IgnoredWarnings); + ret &= ValidateDiagnostics("errors", DiagnosticSeverity.Error, Metadata.ExpectedErrors, logger); + ret &= ValidateDiagnostics("warnings", DiagnosticSeverity.Warning, Metadata.ExpectedWarnings, logger, Metadata.IgnoredWarnings); // Don't try to validate output if we've already failed in terms of errors and warnings, or if we expect errors. if (ret && Metadata.ExpectedErrors is null) { ret &= ValidateOutput(); } - - if (!ret || !configuration.Quiet) - { - outputLines.ForEach(Console.WriteLine); - } return ret; - bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List expected, List? ignored = null) + bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List expected, StatusCheckLogger logger, List? ignored = null) { expected ??= new List(); ignored ??= new List(); @@ -81,10 +77,12 @@ bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List bool ret = ValidateExpectedAgainstActual(type, expected, actualIds); if (!ret) { - outputLines.Add($" Details of actual {type}:"); + logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Details of actual {type}:", "ExampleTester")); foreach (var diagnostic in actualDiagnostics) { - outputLines.Add($" Line {diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1}: {diagnostic.Id}: {diagnostic.GetMessage()}"); + logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, + $" Line {diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1}: {diagnostic.Id}: {diagnostic.GetMessage()}", + "ExampleTester")); } } return ret; @@ -97,7 +95,7 @@ bool ValidateOutput() { if (Metadata.ExpectedOutput != null) { - outputLines.Add(" Output expected, but project has no entry point."); + logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Output expected, but project has no entry point.", "ExampleTester")); return false; } return true; @@ -114,7 +112,7 @@ bool ValidateOutput() var emitResult = compilation.Emit(ms); if (!emitResult.Success) { - outputLines.Add(" Failed to emit assembly"); + logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Failed to emit assembly", "ExampleTester")); return false; } @@ -122,13 +120,13 @@ bool ValidateOutput() var type = generatedAssembly.GetType(typeName); if (type is null) { - outputLines.Add($" Failed to find entry point type {typeName}"); + logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point type {typeName}", "ExampleTester")); return false; } var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (method is null) { - outputLines.Add($" Failed to find entry point method {typeName}.{methodName}"); + logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point method {typeName}.{methodName}", "ExampleTester")); return false; } var arguments = method.GetParameters().Any() @@ -197,7 +195,7 @@ bool MaybeReportError(bool result, string message) { if (!result) { - outputLines.Add(message); + logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, message, "ExampleTester")); } return result; } @@ -207,7 +205,8 @@ bool ValidateExpectedAgainstActual(string type, List expected, List ExecuteAsync(TesterConfiguration configuration) { @@ -31,7 +42,7 @@ async Task ExecuteAsync(TesterConfiguration configuration) foreach (var example in examples) { // The Run method explains any failures, we just need to count them. - if (!await example.Test(configuration)) + if (!await example.Test(configuration, logger)) { failures++; } @@ -42,4 +53,4 @@ async Task ExecuteAsync(TesterConfiguration configuration) Console.WriteLine($"Failures: {failures}"); return failures; -} \ No newline at end of file +} diff --git a/tools/MarkdownConverter/Spec/Reporter.cs b/tools/MarkdownConverter/Spec/Reporter.cs index 47aad0fac..4adaa52c8 100644 --- a/tools/MarkdownConverter/Spec/Reporter.cs +++ b/tools/MarkdownConverter/Spec/Reporter.cs @@ -59,14 +59,14 @@ public void Error(string code, string msg, SourceLocation? loc = null) { loc = loc ?? Location; IncrementErrors(); - githubLogger.LogFailure(new Diagnostic(loc.File ?? "mdspec2docx", loc.StartLine, loc.EndLine, msg, code)); + githubLogger.LogFailure(new StatusCheckMessage(loc.File ?? "mdspec2docx", loc.StartLine, loc.EndLine, msg, code)); } public void Warning(string code, string msg, SourceLocation? loc = null, int lineOffset = 0) { loc = loc ?? Location; IncrementWarnings(); - githubLogger.LogWarning(new Diagnostic(loc.File ?? "mdspec2docx", loc.StartLine+lineOffset, loc.EndLine+lineOffset, msg, code)); + githubLogger.LogWarning(new StatusCheckMessage(loc.File ?? "mdspec2docx", loc.StartLine+lineOffset, loc.EndLine+lineOffset, msg, code)); } public void Log(string code, string msg, SourceLocation? loc = null) diff --git a/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs b/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs index 4dafcc990..10e91490b 100644 --- a/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs +++ b/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs @@ -62,7 +62,7 @@ private string ProcessSectionLinks(string line, int lineNumber, string path) if ((referenceText.Length > 1) && (!linkMap.ContainsKey(referenceText))) { - var diagnostic = new Diagnostic(path, lineNumber, lineNumber, $"`{referenceText}` not found", DiagnosticIDs.TOC002); + var diagnostic = new StatusCheckMessage(path, lineNumber, lineNumber, $"`{referenceText}` not found", DiagnosticIDs.TOC002); logger.LogFailure(diagnostic); } else { diff --git a/tools/StandardAnchorTags/TocSectionNumberBuilder.cs b/tools/StandardAnchorTags/TocSectionNumberBuilder.cs index 64d0e7791..9b3b21ad4 100644 --- a/tools/StandardAnchorTags/TocSectionNumberBuilder.cs +++ b/tools/StandardAnchorTags/TocSectionNumberBuilder.cs @@ -69,7 +69,7 @@ public async Task AddFrontMatterTocEntries(string fileName) return; } // Getting here means this file doesn't have an H1. That's an error: - var diagnostic = new Diagnostic(path, 1, 1, "File doesn't have an H1 tag as its first line.", DiagnosticIDs.TOC001); + var diagnostic = new StatusCheckMessage(path, 1, 1, "File doesn't have an H1 tag as its first line.", DiagnosticIDs.TOC001); logger.LogFailure(diagnostic); } diff --git a/tools/Utilities/StatusCheckLogger.cs b/tools/Utilities/StatusCheckLogger.cs index e98ea1e57..47733baa1 100644 --- a/tools/Utilities/StatusCheckLogger.cs +++ b/tools/Utilities/StatusCheckLogger.cs @@ -10,7 +10,7 @@ namespace Utilities; /// The error message ID /// The start line (index from 1) /// The end line (index from 1) -public record Diagnostic(string file, int StartLine, int EndLine, string Message, string Id); +public record StatusCheckMessage(string file, int StartLine, int EndLine, string Message, string Id); /// /// This class writes the status of the check to the console in the format GitHub supports @@ -30,7 +30,25 @@ public class StatusCheckLogger(string pathToRoot, string toolName) // Utility method to format the path to unix style, from the root of the repository. private string FormatPath(string path) => Path.GetRelativePath(pathToRoot, path).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - private void WriteMessageToConsole(string prefix, Diagnostic d) => Console.WriteLine($"{prefix}{toolName}-{d.Id}::file={FormatPath(d.file)},line={d.StartLine}::{d.Message}"); + private void WriteMessageToConsole(string prefix, StatusCheckMessage d) => Console.WriteLine($"{prefix}{toolName}-{d.Id}::file={FormatPath(d.file)},line={d.StartLine}::{d.Message}"); + + /// + /// Log a notice from the status check to the console only + /// + /// The diagnostic + /// + /// Log the diagnostic information to console. These will only appear in the console window, not + /// as annotations on the changes in the PR. + /// + public void ConsoleOnlyLog(StatusCheckMessage d) + { + WriteMessageToConsole("", d); + annotations.Add( + new(FormatPath(d.file), + d.StartLine, d.EndLine, + CheckAnnotationLevel.Notice, $"{d.Id}::{d.Message}") + ); + } /// /// Log a notice from the status check @@ -40,7 +58,7 @@ public class StatusCheckLogger(string pathToRoot, string toolName) /// Add the diagnostic to the annotation list and /// log the diagnostic information to console. /// - public void LogNotice(Diagnostic d) + public void LogNotice(StatusCheckMessage d) { WriteMessageToConsole("", d); annotations.Add( @@ -59,7 +77,7 @@ public void LogNotice(Diagnostic d) /// log the warning notice to the console. /// Warnings are logged, but the process reports "success" to GitHub. /// - public void LogWarning(Diagnostic d) + public void LogWarning(StatusCheckMessage d) { WriteMessageToConsole("⚠️", d); annotations.Add( @@ -76,11 +94,11 @@ public void LogWarning(Diagnostic d) /// /// Add the diagnostic to the annotation list and /// log the failure notice to the console. - /// This method is distinct from in + /// This method is distinct from in /// that this method does not throw an exception. Its purpose is to log /// the failure but allow the tool to continue running further checks. /// - public void LogFailure(Diagnostic d) + public void LogFailure(StatusCheckMessage d) { WriteMessageToConsole("❌", d); annotations.Add( @@ -98,11 +116,11 @@ public void LogFailure(Diagnostic d) /// /// Add the diagnostic to the annotation list and /// log the failure notice to the console. - /// This method is distinct from in + /// This method is distinct from in /// that this method throws an exception. Its purpose is to log /// the failure and immediately exit, foregoing any further checks. /// - public void ExitOnFailure(Diagnostic d) + public void ExitOnFailure(StatusCheckMessage d) { LogFailure(d); throw new InvalidOperationException(d.Message); @@ -138,9 +156,11 @@ public async Task BuildCheckRunResult(string token, string owner, string repo, s } // If the token does not have the correct permissions, we will get a 403 // Once running on a branch on the dotnet org, this should work correctly. - catch (ForbiddenException) + catch (ForbiddenException e) { Console.WriteLine("===== WARNING: Could not create a check run.====="); + Console.WriteLine("Exception details:"); + Console.WriteLine(e); } } } diff --git a/tools/tools.sln b/tools/tools.sln index afa87f0cc..be1786ffb 100644 --- a/tools/tools.sln +++ b/tools/tools.sln @@ -14,6 +14,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleExtractor", "ExampleExtractor\ExampleExtractor.csproj", "{571E69B9-07A3-4682-B692-876B016315CD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleTester", "ExampleTester\ExampleTester.csproj", "{829FE7D6-B7E7-48DF-923A-73A79921E997}" + ProjectSection(ProjectDependencies) = postProject + {835C6333-BDB5-4DEC-B3BE-4300E3F948AF} = {835C6333-BDB5-4DEC-B3BE-4300E3F948AF} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleFormatter", "ExampleFormatter\ExampleFormatter.csproj", "{82D1A159-5637-48C4-845D-CC1390995CC2}" EndProject From 0fb68fc88d8bef1c3ca53d4dc879cc8ed183a1a5 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 19 Sep 2024 16:41:46 -0400 Subject: [PATCH 2/4] Update tools/Utilities/StatusCheckLogger.cs --- tools/Utilities/StatusCheckLogger.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/Utilities/StatusCheckLogger.cs b/tools/Utilities/StatusCheckLogger.cs index 47733baa1..d8d4bfd93 100644 --- a/tools/Utilities/StatusCheckLogger.cs +++ b/tools/Utilities/StatusCheckLogger.cs @@ -43,11 +43,6 @@ public class StatusCheckLogger(string pathToRoot, string toolName) public void ConsoleOnlyLog(StatusCheckMessage d) { WriteMessageToConsole("", d); - annotations.Add( - new(FormatPath(d.file), - d.StartLine, d.EndLine, - CheckAnnotationLevel.Notice, $"{d.Id}::{d.Message}") - ); } /// From 0a5359fcbd87e37c512c203216be4a5e3366c3d3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 19 Sep 2024 16:42:43 -0400 Subject: [PATCH 3/4] Apply suggestions from code review --- tools/Utilities/StatusCheckLogger.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/Utilities/StatusCheckLogger.cs b/tools/Utilities/StatusCheckLogger.cs index d8d4bfd93..829489c3d 100644 --- a/tools/Utilities/StatusCheckLogger.cs +++ b/tools/Utilities/StatusCheckLogger.cs @@ -40,10 +40,8 @@ public class StatusCheckLogger(string pathToRoot, string toolName) /// Log the diagnostic information to console. These will only appear in the console window, not /// as annotations on the changes in the PR. /// - public void ConsoleOnlyLog(StatusCheckMessage d) - { + public void ConsoleOnlyLog(StatusCheckMessage d) => WriteMessageToConsole("", d); - } /// /// Log a notice from the status check From 16648f9a48f98ef902c5c43a09fa434f12beccee Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 4 Oct 2024 10:18:34 -0400 Subject: [PATCH 4/4] Feedback from code review A bit of refactoring. --- tools/ExampleTester/GeneratedExample.cs | 22 +++++++++---------- tools/Utilities/StatusCheckLogger.cs | 29 ++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/tools/ExampleTester/GeneratedExample.cs b/tools/ExampleTester/GeneratedExample.cs index eb8af52fd..6c06eab33 100644 --- a/tools/ExampleTester/GeneratedExample.cs +++ b/tools/ExampleTester/GeneratedExample.cs @@ -36,7 +36,7 @@ private static GeneratedExample Load(string directory) internal async Task Test(TesterConfiguration configuration, StatusCheckLogger logger) { - logger.ConsoleOnlyLog(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $"Testing {Metadata.Name} from {Metadata.Source}", "ExampleTester")); + logger.ConsoleOnlyLog(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $"Testing {Metadata.Name} from {Metadata.Source}", "ExampleTester"); // Explicitly do a release build, to avoid implicitly defining DEBUG. var properties = new Dictionary { { "Configuration", "Release" } }; @@ -77,12 +77,12 @@ bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List bool ret = ValidateExpectedAgainstActual(type, expected, actualIds); if (!ret) { - logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Details of actual {type}:", "ExampleTester")); + logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Details of actual {type}:", "ExampleTester"); foreach (var diagnostic in actualDiagnostics) { - logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, + logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Line {diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1}: {diagnostic.Id}: {diagnostic.GetMessage()}", - "ExampleTester")); + "ExampleTester"); } } return ret; @@ -95,7 +95,7 @@ bool ValidateOutput() { if (Metadata.ExpectedOutput != null) { - logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Output expected, but project has no entry point.", "ExampleTester")); + logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Output expected, but project has no entry point.", "ExampleTester"); return false; } return true; @@ -112,7 +112,7 @@ bool ValidateOutput() var emitResult = compilation.Emit(ms); if (!emitResult.Success) { - logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Failed to emit assembly", "ExampleTester")); + logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Failed to emit assembly", "ExampleTester"); return false; } @@ -120,13 +120,13 @@ bool ValidateOutput() var type = generatedAssembly.GetType(typeName); if (type is null) { - logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point type {typeName}", "ExampleTester")); + logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point type {typeName}", "ExampleTester"); return false; } var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (method is null) { - logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point method {typeName}.{methodName}", "ExampleTester")); + logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point method {typeName}.{methodName}", "ExampleTester"); return false; } var arguments = method.GetParameters().Any() @@ -195,7 +195,7 @@ bool MaybeReportError(bool result, string message) { if (!result) { - logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, message, "ExampleTester")); + logger.LogFailure(Metadata.Source, Metadata.StartLine, Metadata.EndLine, message, "ExampleTester"); } return result; } @@ -205,8 +205,8 @@ bool ValidateExpectedAgainstActual(string type, List expected, List Console.WriteLine($"{prefix}{toolName}-{d.Id}::file={FormatPath(d.file)},line={d.StartLine}::{d.Message}"); + /// + /// Log a notice from the status check to the console only + /// + /// source file + /// start line + /// end line + /// The error message + /// The error ID + /// + /// Log the diagnostic information to console. These will only appear in the console window, not + /// as annotations on the changes in the PR. + /// + public void ConsoleOnlyLog(string source, int startLine, int endLine, string message, string id) => + ConsoleOnlyLog(source, new StatusCheckMessage(source, startLine, endLine, message, id)); + /// /// Log a notice from the status check to the console only /// @@ -40,7 +55,7 @@ public class StatusCheckLogger(string pathToRoot, string toolName) /// Log the diagnostic information to console. These will only appear in the console window, not /// as annotations on the changes in the PR. /// - public void ConsoleOnlyLog(StatusCheckMessage d) => + public void ConsoleOnlyLog(string source, StatusCheckMessage d) => WriteMessageToConsole("", d); /// @@ -80,6 +95,18 @@ public void LogWarning(StatusCheckMessage d) ); } + /// + /// Log a failure from the status check + /// + /// The source file + /// Start line in source + /// End line in source + /// The error message + /// The string ID for the error + public void LogFailure(string source, int startLine, int endLine, string message, string id) => + LogFailure(new(source, startLine, endLine, message, id)); + + /// /// Log a failure from the status check ///