diff --git a/src/main/java/edu/hm/hafner/analysis/parser/violations/RoslynAnalyzersAdapter.java b/src/main/java/edu/hm/hafner/analysis/parser/violations/RoslynAnalyzersAdapter.java
new file mode 100644
index 0000000000..8dda05db04
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/violations/RoslynAnalyzersAdapter.java
@@ -0,0 +1,15 @@
+package edu.hm.hafner.analysis.parser.violations;
+
+import java.io.Serial;
+
+/**
+ * A parser for Roslyn Analyzers SARIF reports.
+ *
+ * @author Akash Manna
+ * @see Roslyn Analyzers
+ * @see Roslyn
+ */
+public class RoslynAnalyzersAdapter extends SarifAdapter {
+ @Serial
+ private static final long serialVersionUID = 4334638378275050852L;
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java b/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java
index 61d77428f6..04bb170674 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java
@@ -175,6 +175,7 @@ public class ParserRegistry {
new ReviveDescriptor(),
new RfLintDescriptor(),
new RoboCopyDescriptor(),
+ new RoslynAnalyzersDescriptor(),
new RuboCopDescriptor(),
new RuffDescriptor(),
new RustAnalyzerDescriptor(),
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/RoslynAnalyzersDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/RoslynAnalyzersDescriptor.java
new file mode 100644
index 0000000000..4dd2e23b8b
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/registry/RoslynAnalyzersDescriptor.java
@@ -0,0 +1,39 @@
+package edu.hm.hafner.analysis.registry;
+
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.parser.violations.RoslynAnalyzersAdapter;
+
+/**
+ * A descriptor for Roslyn Analyzers SARIF reports.
+ *
+ * @author Akash Manna
+ */
+class RoslynAnalyzersDescriptor extends ParserDescriptor {
+ private static final String ID = "roslyn-analyzers";
+ private static final String NAME = "Roslyn Analyzers";
+
+ RoslynAnalyzersDescriptor() {
+ super(ID, NAME);
+ }
+
+ @Override
+ public IssueParser create(final Option... options) {
+ return new RoslynAnalyzersAdapter();
+ }
+
+ @Override
+ public String getPattern() {
+ return "**/roslyn-analyzers-report.sarif";
+ }
+
+ @Override
+ public String getHelp() {
+ return "Use command line option /errorlog:roslyn-analyzers-report.sarif "
+ + "with dotnet build to generate a Roslyn analyzer SARIF report.";
+ }
+
+ @Override
+ public String getUrl() {
+ return "https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-options/errors-warnings#errorlog";
+ }
+}
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/violations/RoslynAnalyzersAdapterTest.java b/src/test/java/edu/hm/hafner/analysis/parser/violations/RoslynAnalyzersAdapterTest.java
new file mode 100644
index 0000000000..cb07801ce0
--- /dev/null
+++ b/src/test/java/edu/hm/hafner/analysis/parser/violations/RoslynAnalyzersAdapterTest.java
@@ -0,0 +1,70 @@
+package edu.hm.hafner.analysis.parser.violations;
+
+import org.junit.jupiter.api.Test;
+
+import edu.hm.hafner.analysis.Issue;
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.Severity;
+import edu.hm.hafner.analysis.assertions.SoftAssertions;
+import edu.hm.hafner.analysis.registry.AbstractParserTest;
+import edu.hm.hafner.analysis.registry.ParserRegistry;
+
+import static edu.hm.hafner.analysis.assertions.Assertions.*;
+
+/**
+ * Tests the class {@link RoslynAnalyzersAdapter}.
+ *
+ * @author Akash Manna
+ */
+class RoslynAnalyzersAdapterTest extends AbstractParserTest {
+ RoslynAnalyzersAdapterTest() {
+ super("../roslyn-analyzers-report.sarif");
+ }
+
+ @Override
+ protected void assertThatIssuesArePresent(final Report report, final SoftAssertions softly) {
+ softly.assertThat(report).hasSize(3);
+
+ softly.assertThat(report.filter(Issue.byType("CA1822")).get(0))
+ .hasFileName("src/Program.cs")
+ .hasLineStart(12)
+ .hasLineEnd(12)
+ .hasType("CA1822")
+ .hasSeverity(Severity.WARNING_NORMAL)
+ .hasMessage("Mark members as static");
+
+ softly.assertThat(report.filter(Issue.byType("CA2000")).get(0))
+ .hasFileName("C:/work/analysis-model/src/Helpers.cs")
+ .hasLineStart(7)
+ .hasLineEnd(7)
+ .hasType("CA2000")
+ .hasSeverity(Severity.WARNING_HIGH)
+ .hasMessage("Dispose objects before losing scope");
+
+ softly.assertThat(report.filter(Issue.byType("IDE0051")).get(0))
+ .hasFileName("src/Utilities/Formatting.cs")
+ .hasLineStart(25)
+ .hasLineEnd(25)
+ .hasType("IDE0051")
+ .hasSeverity(Severity.WARNING_LOW)
+ .hasMessage("Remove unused private members");
+ }
+
+ @Test
+ void shouldProvideDescriptorMetadata() {
+ var descriptor = new ParserRegistry().get("roslyn-analyzers");
+
+ assertThat(descriptor.getPattern()).isEqualTo("**/roslyn-analyzers-report.sarif");
+ assertThat(descriptor.getHelp()).contains("/errorlog:roslyn-analyzers-report.sarif");
+ assertThat(descriptor.getUrl())
+ .isEqualTo("https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-options/errors-warnings#errorlog");
+ assertThat(descriptor.hasHelp()).isTrue();
+ assertThat(descriptor.hasUrl()).isTrue();
+ }
+
+ @Override
+ protected IssueParser createParser() {
+ return new RoslynAnalyzersAdapter();
+ }
+}
diff --git a/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java b/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java
index 2aae5050d7..645b17232b 100644
--- a/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java
@@ -22,7 +22,7 @@
class ParserRegistryTest extends ResourceTest {
// Note for parser developers: if you add a new parser,
// please check if you are using the correct type and increment the corresponding count
- private static final long WARNING_PARSERS_COUNT = 154L;
+ private static final long WARNING_PARSERS_COUNT = 155L;
private static final long BUG_PARSERS_COUNT = 3L;
private static final long VULNERABILITY_PARSERS_COUNT = 18L;
private static final long DUPLICATION_PARSERS_COUNT = 3L;
diff --git a/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java b/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java
index 75b32253cc..7d1c7a2850 100644
--- a/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java
@@ -110,6 +110,12 @@ void shouldFindAllSarifIssues() {
findIssuesOfTool(2, "sarif", "sarif.json");
}
+ /** Runs the Roslyn Analyzers parser on an output file that contains 3 issues. */
+ @Test
+ void shouldFindAllRoslynAnalyzersIssues() {
+ findIssuesOfTool(3, "roslyn-analyzers", "roslyn-analyzers-report.sarif");
+ }
+
/** Runs the Cmake parser on an output file that contains 8 issues. */
@Test
void shouldFindAllCmakeIssues() {
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/roslyn-analyzers-report.sarif b/src/test/resources/edu/hm/hafner/analysis/parser/roslyn-analyzers-report.sarif
new file mode 100644
index 0000000000..23b21d7fd2
--- /dev/null
+++ b/src/test/resources/edu/hm/hafner/analysis/parser/roslyn-analyzers-report.sarif
@@ -0,0 +1,109 @@
+{
+ "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
+ "version": "2.1.0",
+ "runs": [
+ {
+ "tool": {
+ "driver": {
+ "name": "Microsoft.NetCore.Analyzers"
+ }
+ },
+ "results": [
+ {
+ "ruleId": "CA1822",
+ "level": "warning",
+ "message": {
+ "text": "Mark members as static"
+ },
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "src/Program.cs"
+ },
+ "region": {
+ "startLine": 12,
+ "endLine": 12,
+ "startColumn": 9,
+ "endColumn": 18
+ }
+ }
+ }
+ ]
+ },
+ {
+ "ruleId": "CA2000",
+ "level": "error",
+ "message": {
+ "text": "Dispose objects before losing scope"
+ },
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "file:///C:/work/analysis-model/src/Helpers.cs"
+ },
+ "region": {
+ "startLine": 7,
+ "endLine": 7,
+ "startColumn": 13,
+ "endColumn": 20
+ }
+ }
+ }
+ ]
+ },
+ {
+ "ruleId": "CA1303",
+ "level": "warning",
+ "message": {
+ "text": "Do not pass literals as localized parameters"
+ },
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "src/Localization.cs"
+ },
+ "region": {
+ "startLine": 18,
+ "endLine": 18,
+ "startColumn": 17,
+ "endColumn": 29
+ }
+ }
+ }
+ ],
+ "suppressions": [
+ {
+ "state": "accepted",
+ "justification": "Covered by legacy suppression"
+ }
+ ]
+ },
+ {
+ "ruleId": "IDE0051",
+ "level": "note",
+ "message": {
+ "text": "Remove unused private members"
+ },
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "src/Utilities/Formatting.cs"
+ },
+ "region": {
+ "startLine": 25,
+ "endLine": 25,
+ "startColumn": 5,
+ "endColumn": 15
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}