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 + } + } + } + ] + } + ] + } + ] +}