From 40186fc78443b8b5a3753138996546edf3f79947 Mon Sep 17 00:00:00 2001 From: Jonathan Melo Date: Mon, 15 Apr 2024 15:57:27 -0400 Subject: [PATCH] Add MSBuildTreatWarningsAsErrors alongside TreatWarningsAsErrors --- .../Workleap.DotNet.CodingStandards.props | 3 +- .../CodingStandardTests.cs | 36 ++++++++++ .../Helpers/ProjectBuilder.cs | 66 +++++++++++++++++-- 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/build/Workleap.DotNet.CodingStandards.props b/src/build/Workleap.DotNet.CodingStandards.props index a144399..e0b296b 100644 --- a/src/build/Workleap.DotNet.CodingStandards.props +++ b/src/build/Workleap.DotNet.CodingStandards.props @@ -17,8 +17,9 @@ true true - + true + true true diff --git a/tests/Workleap.DotNet.CodingStandards.Tests/CodingStandardTests.cs b/tests/Workleap.DotNet.CodingStandards.Tests/CodingStandardTests.cs index 42e3252..9813269 100644 --- a/tests/Workleap.DotNet.CodingStandards.Tests/CodingStandardTests.cs +++ b/tests/Workleap.DotNet.CodingStandards.Tests/CodingStandardTests.cs @@ -25,6 +25,42 @@ public async Task WarningsAsErrorOnGitHubActions() Assert.True(data.HasError("RS0030")); } + [Fact] + public async Task MSBuildWarningsAsErrorOnDebugConfiguration() + { + using var project = new ProjectBuilder(fixture, testOutputHelper); + project.AddCsprojFile(packageReferences: new Dictionary { { "Azure.Identity", "1.10.4" } }); + project.AddFile("sample.cs", """ + namespace sample; + public static class Sample + { + public static void Main(string[] args) + { + } + } + """); + var data = await project.BuildAndGetOutput(); + Assert.True(data.HasWarning("NU1902")); + } + + [Fact] + public async Task MSBuildWarningsAsErrorOnReleaseConfiguration() + { + using var project = new ProjectBuilder(fixture, testOutputHelper); + project.AddCsprojFile(packageReferences: new Dictionary { { "Azure.Identity", "1.10.4" } }); + project.AddFile("sample.cs", """ + namespace sample; + public static class Sample + { + public static void Main(string[] args) + { + } + } + """); + var data = await project.BuildAndGetOutput(["--configuration", "Release"]); + Assert.True(data.HasError("NU1902")); + } + [Fact] public async Task NamingConvention_Invalid() { diff --git a/tests/Workleap.DotNet.CodingStandards.Tests/Helpers/ProjectBuilder.cs b/tests/Workleap.DotNet.CodingStandards.Tests/Helpers/ProjectBuilder.cs index ac33beb..d4c6b84 100644 --- a/tests/Workleap.DotNet.CodingStandards.Tests/Helpers/ProjectBuilder.cs +++ b/tests/Workleap.DotNet.CodingStandards.Tests/Helpers/ProjectBuilder.cs @@ -2,6 +2,7 @@ using Xunit.Abstractions; using System.Text.Json; using CliWrap; +using Xunit.Sdk; namespace Workleap.DotNet.CodingStandards.Tests.Helpers; @@ -46,14 +47,27 @@ public void AddFile(string relativePath, string content) File.WriteAllText(this._directory.GetPath(relativePath), content); } - public void AddCsprojFile(Dictionary? properties = null) + public void AddCsprojFile(Dictionary? properties = null, Dictionary? packageReferences = null) { - var element = new XElement("PropertyGroup"); + var propertyElement = new XElement("PropertyGroup"); if (properties != null) { foreach (var prop in properties) { - element.Add(new XElement(prop.Key), prop.Value); + propertyElement.Add(new XElement(prop.Key), prop.Value); + } + } + + var referencesElement = new XElement("ItemGroup"); + if (packageReferences != null) + { + foreach (var reference in packageReferences) + { + var packageReference = new XElement("PackageReference"); + packageReference.SetAttributeValue("Include", reference.Key); + packageReference.SetAttributeValue("Version", reference.Value); + + referencesElement.Add(packageReference); } } @@ -66,11 +80,12 @@ public void AddCsprojFile(Dictionary? properties = null) enable {SarifFileName},version=2.1 - {element} + {propertyElement} + {referencesElement} """; @@ -92,9 +107,52 @@ public async Task BuildAndGetOutput(string[]? buildArguments = null) var bytes = await File.ReadAllBytesAsync(this._directory.GetPath(SarifFileName)); var sarif = JsonSerializer.Deserialize(bytes) ?? throw new InvalidOperationException("The sarif file is invalid"); + + this.AppendAdditionalResult(sarif); + this._testOutputHelper.WriteLine("Sarif result:\n" + string.Join("\n", sarif.AllResults().Select(r => r.ToString()))); return sarif; } public void Dispose() => this._directory.Dispose(); + + private void AppendAdditionalResult(SarifFile sarifFile) + { + if (this._testOutputHelper is not TestOutputHelper testOutputHelper || sarifFile.Runs == null) + { + return; + } + + var outputLines = testOutputHelper.Output.Split(Environment.NewLine); + var customRunResults = new List(); + + // These rules (for nuget package vulnerability) are not parsed in the sarif file automatically + // See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1901-nu1904 + var scannedRules = new List { "NU1901", "NU1902", "NU1903", "NU1904" } + .ToDictionary(x => x, x => $"{x}:"); + + foreach (var outputLine in outputLines) + { + foreach (var scannedRule in scannedRules) + { + var scannedRuleIndex = outputLine.IndexOf(scannedRule.Value, StringComparison.OrdinalIgnoreCase); + if (scannedRuleIndex == -1) + { + continue; + } + + var previousColonIndex = outputLine.LastIndexOf(':', scannedRuleIndex); + var ruleLevel = outputLine.Substring(previousColonIndex + 1, scannedRuleIndex - previousColonIndex - 1).Trim(); + + var message = outputLine[(scannedRuleIndex + scannedRule.Value.Length + 1)..]; + customRunResults.Add(new SarifFileRunResult { Level = ruleLevel, RuleId = scannedRule.Key, Message = new SarifFileRunResultMessage { Text = message } }); + } + } + + var distinctRules = customRunResults + .DistinctBy(x => new { x.RuleId, x.Level }) + .ToArray(); + + sarifFile.Runs = sarifFile.Runs.Append(new SarifFileRun { Results = distinctRules }).ToArray(); + } }