Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.67] - 2025-10-22
### Fix
Enabling suppressions for XML files (addressing bug [#588](https://github.com/microsoft/DevSkim/issues/588))

## [1.0.66] - 2025-09-04
### Dependencies
Update Dependencies for C# Projects
Expand Down
120 changes: 120 additions & 0 deletions DevSkim-DotNet/Microsoft.DevSkim.Tests/DevSkimRuleProcessorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
namespace Microsoft.DevSkim.Tests
{
[TestClass]
public class DevSkimRuleProcessorTests
{

[TestMethod]
[DataRow("csharp", "DS123456", "// DevSkim: ignore DS123456", DisplayName = "C# Basic Suppression")]
[DataRow("python", "DS123456", "# DevSkim: ignore DS123456", DisplayName = "Python Basic Suppression")]
[DataRow("sql", "DS123456", "-- DevSkim: ignore DS123456", DisplayName = "SQL Basic Suppression")]
[DataRow("vb", "DS123456", "' DevSkim: ignore DS123456", DisplayName = "VB Basic Suppression")]
[DataRow("csharp", "DS123456,DS789012", "// DevSkim: ignore DS123456,DS789012", DisplayName = "Multiple Rule IDs")]
[DataRow("csharp", "", "// DevSkim: ignore ", DisplayName = "Empty Rule ID")]
public void GenerateSuppressionByLanguageTest_BasicSuppressions(string language, string ruleId, string expected)
{
// Test basic suppression generation for various languages
string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, ruleId);
Assert.AreEqual(expected, result);
}

[TestMethod]
[DataRow("csharp", "DS123456", 30, DisplayName = "C# with 30 days duration")]
[DataRow("python", "DS789012", 15, DisplayName = "Python with 15 days duration")]
[DataRow("sql", "DS111213", 60, DisplayName = "SQL with 60 days duration")]
[DataRow("vb", "DS141516", 7, DisplayName = "VB with 7 days duration")]
public void GenerateSuppressionByLanguageTest_WithDuration(string language, string ruleId, int duration)
{
// Test suppression with expiration date
DateTime testDate = DateTime.Now.AddDays(duration);
string expectedDate = testDate.ToString("yyyy-MM-dd");

string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, ruleId, duration: duration);

Assert.IsTrue(result.Contains("until"));
Assert.IsTrue(result.Contains(expectedDate));
Assert.IsTrue(result.Contains($"ignore {ruleId} until"));
}

[TestMethod]
[DataRow("csharp", "DS123456", "JohnDoe", "// DevSkim: ignore DS123456 by JohnDoe", DisplayName = "C# with reviewer")]
[DataRow("python", "DS789012", "JaneSmith", "# DevSkim: ignore DS789012 by JaneSmith", DisplayName = "Python with reviewer")]
[DataRow("sql", "DS111213", "BobJones", "-- DevSkim: ignore DS111213 by BobJones", DisplayName = "SQL with reviewer")]
[DataRow("vb", "DS141516", "AliceWilliams", "' DevSkim: ignore DS141516 by AliceWilliams", DisplayName = "VB with reviewer")]
public void GenerateSuppressionByLanguageTest_WithReviewer(string language, string ruleId, string reviewerName, string expected)
{
// Test suppression with reviewer name
string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, ruleId, reviewerName: reviewerName);

Assert.AreEqual(expected, result);
}

[TestMethod]
[DataRow("csharp", "DS123456", 15, "JaneSmith", DisplayName = "C# with duration and reviewer")]
[DataRow("python", "DS789012", 30, "JohnDoe", DisplayName = "Python with duration and reviewer")]
[DataRow("sql", "DS111213", 7, "BobJones", DisplayName = "SQL with duration and reviewer")]
[DataRow("vb", "DS141516", 45, "AliceWilliams", DisplayName = "VB with duration and reviewer")]
public void GenerateSuppressionByLanguageTest_WithDurationAndReviewer(string language, string ruleId, int duration, string reviewerName)
{
// Test suppression with both duration and reviewer
DateTime testDate = DateTime.Now.AddDays(duration);
string expectedDate = testDate.ToString("yyyy-MM-dd");

string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, ruleId, duration: duration, reviewerName: reviewerName);

Assert.IsTrue(result.Contains($"until {expectedDate}"));
Assert.IsTrue(result.Contains($"by {reviewerName}"));
Assert.IsTrue(result.Contains($"ignore {ruleId} until"));
Assert.IsTrue(result.EndsWith($" by {reviewerName}"));
}

[TestMethod]
[DataRow("csharp", "DS123456", "/*", " */", DisplayName = "C# Multiline")]
[DataRow("python", "DS123456", "#", "\n", DisplayName = "Python Multiline")]
public void GenerateSuppressionByLanguageTest_MultiLinePreferred(string language, string ruleId, string expectedStart, string expectedEnd)
{
// Test multiline comment preference
string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, ruleId, preferMultiLine: true);

Assert.IsTrue(result.StartsWith($"{expectedStart} DevSkim: ignore {ruleId}"));
Assert.IsTrue(result.EndsWith(expectedEnd));
}

[TestMethod]
[DataRow("xml", "DS123456", "<!-- DevSkim: ignore DS123456 -->", DisplayName = "XML Language")]
public void GenerateSuppressionByLanguageTest_XMLLanguage(string language, string ruleId, string expected)
{
// Test XML-like languages
string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, ruleId);

Console.WriteLine($"{language} suppression result: '{result}'");
Assert.AreEqual(expected, result);
Assert.IsNotNull(result);
}

[TestMethod]
[DataRow(null, DisplayName = "Null Language")]
[DataRow("unknownlang", DisplayName = "Unknown Language")]
public void GenerateSuppressionByLanguageTest_InvalidLanguages(string language)
{
// Test with invalid language parameters
string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, "DS123456");

Assert.IsNotNull(result);
// Should handle invalid languages gracefully
}

[TestMethod]
[DataRow("csharp", "DS123456", "// DevSkim: ignore DS123456", DisplayName = "C# with custom languages")]
[DataRow("python", "DS789012", "# DevSkim: ignore DS789012", DisplayName = "Python with custom languages")]
[DataRow("sql", "DS111213", "-- DevSkim: ignore DS111213", DisplayName = "SQL with custom languages")]
public void GenerateSuppressionByLanguageTest_CustomLanguagesObject(string language, string ruleId, string expected)
{
// Test with custom languages object
var customLanguages = DevSkimLanguages.LoadEmbedded();
string result = DevSkimRuleProcessor.GenerateSuppressionByLanguage(language, ruleId, languages: customLanguages);

Assert.AreEqual(expected, result);
}
}
}
114 changes: 114 additions & 0 deletions DevSkim-DotNet/Microsoft.DevSkim.Tests/SuppressionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Microsoft.DevSkim.Tests
[TestClass]
public class SuppressionTest
{
public TestContext? TestContext { get; set; }

[DataTestMethod]
[DataRow("", 30)]
[DataRow("Contoso", 30)]
Expand Down Expand Up @@ -343,5 +345,117 @@ public void DontChangeFilesWithoutSelectedFindings(string lineBreakSequence, boo
string result = File.ReadAllText(sourceFile);
Assert.AreEqual(originalContent, result);
}

/// <summary>
/// Integration test for XML suppressions
/// </summary>
[TestMethod]
public void ExecuteSuppressionsForXML()
{
// Properly formatted XML content with patterns that trigger DevSkim rules (MD5 and http://)
string xmlContent = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<configuration>
<security>
<algorithm>MD5</algorithm>
</security>
<endpoint>http://example.com</endpoint>
</configuration>";

(string basePath, string sourceFile, string sarifPath) = runAnalysis(xmlContent, "xml");

SuppressionCommandOptions opts = new SuppressionCommandOptions
{
Path = basePath,
SarifInput = sarifPath,
ApplyAllSuppression = true
};

int resultCode = new SuppressionCommand(opts).Run();
Assert.AreEqual(0, resultCode);

string result = File.ReadAllText(sourceFile);

// Verify that XML-style suppressions were added
Assert.Contains("<!-- DevSkim: ignore", result, "XML suppression comment should be present");
Assert.Contains("-->", result, "XML suppression comment should be properly closed");

// Verify suppressions are in correct format
string[] lines = File.ReadAllLines(sourceFile);
bool foundSuppression = false;
foreach (string line in lines)
{
if (line.Contains("<!-- DevSkim: ignore"))
{
foundSuppression = true;
Suppression suppression = new Suppression(line);
Assert.IsTrue(suppression.IsInEffect, "Suppression should be in effect");
Assert.IsGreaterThan(0,suppression.GetSuppressedIds.Length, "Should have at least one suppressed rule ID");
}
}
Assert.IsTrue(foundSuppression, "At least one suppression should be found in the file");
}

/// <summary>
/// Integration test for XML suppressions with reviewer and duration
/// </summary>
[TestMethod]
[DataRow("", 30, DisplayName = "XML Suppression with Duration only")]
[DataRow("TestReviewer", 30, DisplayName = "XML Suppression with Reviewer and Duration")]
[DataRow("TestReviewer", 0, DisplayName = "XML Suppression with Reviewer only")]
public void ExecuteSuppressionsForXMLWithReviewerAndDuration(string reviewerName, int duration)
{
// Properly formatted XML content with patterns that trigger DevSkim rules (MD5 and http://)
string xmlContent = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<configuration>
<security>
<algorithm>MD5</algorithm>
</security>
<endpoint>http://example.com</endpoint>
</configuration>";

(string basePath, string sourceFile, string sarifPath) = runAnalysis(xmlContent, "xml");

SuppressionCommandOptions opts = new SuppressionCommandOptions
{
Path = basePath,
SarifInput = sarifPath,
ApplyAllSuppression = true,
Reviewer = reviewerName,
Duration = duration
};

DateTime expectedExpiration = duration > 0 ? DateTime.Now.AddDays(duration) : DateTime.Now;

int resultCode = new SuppressionCommand(opts).Run();
Assert.AreEqual(0, resultCode);

string result = File.ReadAllText(sourceFile);
Assert.Contains("<!-- DevSkim: ignore", result, "XML suppression comment should be present");

// Verify suppressions contain reviewer and/or duration
string[] lines = File.ReadAllLines(sourceFile);
bool foundSuppression = false;
foreach (string line in lines)
{
if (line.Contains("<!-- DevSkim: ignore"))
{
foundSuppression = true;
Suppression suppression = new Suppression(line);
Assert.IsTrue(suppression.IsInEffect, "Suppression should be in effect");

if (!string.IsNullOrEmpty(reviewerName))
{
Assert.AreEqual(reviewerName, suppression.Reviewer, "Reviewer name should match");
}

if (duration > 0)
{
//Assert.IsNotNull(suppression.ExpirationDate, "Expiration date should be set");
Assert.AreEqual(expectedExpiration.Date, suppression.ExpirationDate, "Expiration date should match");
}
}
}
Assert.IsTrue(foundSuppression, "At least one suppression should be found in the file");
}
}
}
9 changes: 8 additions & 1 deletion DevSkim-DotNet/Microsoft.DevSkim/resources/comments.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"language": [
"plaintext"
],
"always": true
"always": true
},
{
"language": [
Expand Down Expand Up @@ -74,5 +74,12 @@
"inline": "::",
"prefix": "Rem",
"suffix": "\n"
},
{
"language": [
"xml"
],
"prefix": "<!--",
"suffix": "-->"
}
]
Loading