From c46a670086c3f53f003dcebf3c97e1d10cd473a9 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Fri, 20 Dec 2024 16:39:24 +0100 Subject: [PATCH] Programming exercises: Add static code analysis for Python exercises with integrated code lifecycle (#9573) --- .../config/StaticCodeAnalysisConfigurer.java | 73 ++++++- .../domain/StaticCodeAnalysisTool.java | 4 +- .../ProgrammingExerciseRepositoryService.java | 34 ++- ...alCIProgrammingLanguageFeatureService.java | 2 +- .../scaparser/strategy/ParserPolicy.java | 4 +- .../strategy/sarif/RuffCategorizer.java | 15 ++ src/main/resources/config/application.yml | 2 +- .../templates/aeolus/python/default_static.sh | 30 +++ .../aeolus/python/default_static.yaml | 21 ++ .../staticCodeAnalysis/test/ruff-student.toml | 35 ++++ .../StaticCodeAnalysisParserUnitTest.java | 5 + .../util/ProgrammingExerciseFactory.java | 1 + .../static-code-analysis/expected/ruff.json | 60 ++++++ .../static-code-analysis/reports/ruff.sarif | 195 ++++++++++++++++++ 14 files changed, 468 insertions(+), 13 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuffCategorizer.java create mode 100644 src/main/resources/templates/aeolus/python/default_static.sh create mode 100644 src/main/resources/templates/aeolus/python/default_static.yaml create mode 100644 src/main/resources/templates/python/staticCodeAnalysis/test/ruff-student.toml create mode 100644 src/test/resources/test-data/static-code-analysis/expected/ruff.json create mode 100644 src/test/resources/test-data/static-code-analysis/reports/ruff.sarif diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/StaticCodeAnalysisConfigurer.java b/src/main/java/de/tum/cit/aet/artemis/core/config/StaticCodeAnalysisConfigurer.java index 54ef93ca02c7..4946db25ead2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/StaticCodeAnalysisConfigurer.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/StaticCodeAnalysisConfigurer.java @@ -13,8 +13,74 @@ */ public class StaticCodeAnalysisConfigurer { + // @formatter:off + private static final List CATEGORY_NAMES_PYTHON = List.of( + "Pyflakes", + "pycodestyle", + "mccabe", + "isort", + "pep8-naming", + "pydocstyle", + "pyupgrade", + "flake8-2020", + "flake8-annotations", + "flake8-async", + "flake8-bandit", + "flake8-blind-except", + "flake8-boolean-trap", + "flake8-bugbear", + "flake8-builtins", + "flake8-commas", + "flake8-copyright", + "flake8-comprehensions", + "flake8-datetimez", + "flake8-debugger", + "flake8-django", + "flake8-errmsg", + "flake8-executable", + "flake8-future-annotations", + "flake8-implicit-str-concat", + "flake8-import-conventions", + "flake8-logging", + "flake8-logging-format", + "flake8-no-pep420", + "flake8-pie", + "flake8-print", + "flake8-pyi", + "flake8-pytest-style", + "flake8-quotes", + "flake8-raise", + "flake8-return", + "flake8-self", + "flake8-slots", + "flake8-simplify", + "flake8-tidy-imports", + "flake8-type-checking", + "flake8-gettext", + "flake8-unused-arguments", + "flake8-use-pathlib", + "flake8-todos", + "flake8-fixme", + "eradicate", + "pandas-vet", + "pygrep-hooks", + "Pylint", + "tryceratops", + "flynt", + "NumPy-specific rules", + "FastAPI", + "Airflow", + "Perflint", + "refurb", + "pydoclint", + "Ruff-specific rules", + "Unknown" + ); + // @formatter:on + private static final Map> languageToDefaultCategories = Map.of(ProgrammingLanguage.JAVA, - createDefaultCategoriesForJava(), ProgrammingLanguage.SWIFT, createDefaultCategoriesForSwift(), ProgrammingLanguage.C, createDefaultCategoriesForC()); + createDefaultCategoriesForJava(), ProgrammingLanguage.SWIFT, createDefaultCategoriesForSwift(), ProgrammingLanguage.C, createDefaultCategoriesForC(), + ProgrammingLanguage.PYTHON, createDefaultCategoriesForPython()); /** * Create an unmodifiable List of default static code analysis categories for Java @@ -85,6 +151,11 @@ private static List createDefaultCategoriesFo new StaticCodeAnalysisDefaultCategory("Miscellaneous", 0.2D, 2D, CategoryState.INACTIVE, List.of(createMapping(StaticCodeAnalysisTool.GCC, "Misc")))); } + private static List createDefaultCategoriesForPython() { + return CATEGORY_NAMES_PYTHON.stream() + .map(name -> new StaticCodeAnalysisDefaultCategory(name, 0.0, 1.0, CategoryState.FEEDBACK, List.of(createMapping(StaticCodeAnalysisTool.RUFF, name)))).toList(); + } + public static Map> staticCodeAnalysisConfiguration() { return languageToDefaultCategories; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/StaticCodeAnalysisTool.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/StaticCodeAnalysisTool.java index dd3ffcbea67a..d902dc90d2c8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/StaticCodeAnalysisTool.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/StaticCodeAnalysisTool.java @@ -18,6 +18,7 @@ public enum StaticCodeAnalysisTool { PMD_CPD("cpd.xml"), SWIFTLINT("swiftlint-result.xml"), GCC("gcc.xml"), + RUFF("ruff.sarif"), OTHER(null), ; // @formatter:on @@ -26,7 +27,8 @@ public enum StaticCodeAnalysisTool { private static final Map> TOOLS_OF_PROGRAMMING_LANGUAGE = new EnumMap<>(Map.of( ProgrammingLanguage.JAVA, List.of(SPOTBUGS, CHECKSTYLE, PMD, PMD_CPD), ProgrammingLanguage.SWIFT, List.of(SWIFTLINT), - ProgrammingLanguage.C, List.of(GCC) + ProgrammingLanguage.C, List.of(GCC), + ProgrammingLanguage.PYTHON, List.of(RUFF) )); // @formatter:on diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java index c302c5f147e5..978508ce5c95 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java @@ -49,6 +49,8 @@ public class ProgrammingExerciseRepositoryService { private static final String TEST_DIR = "test"; + private static final String STATIC_CODE_ANALYSIS_DIR = "staticCodeAnalysis"; + private static final String POM_XML = "pom.xml"; private static final String BUILD_GRADLE = "build.gradle"; @@ -114,7 +116,8 @@ void setupExerciseTemplate(final ProgrammingExercise programmingExercise, final setupRepositories(programmingExercise, exerciseCreator, exerciseResources, solutionResources, testResources); } - private record RepositoryResources(Repository repository, Resource[] resources, Path prefix, Resource[] projectTypeResources, Path projectTypePrefix) { + private record RepositoryResources(Repository repository, Resource[] resources, Path prefix, Resource[] projectTypeResources, Path projectTypePrefix, + Resource[] staticCodeAnalysisResources, Path staticCodeAnalysisPrefix) { } /** @@ -128,17 +131,17 @@ private record RepositoryResources(Repository repository, Resource[] resources, private RepositoryResources getRepositoryResources(final ProgrammingExercise programmingExercise, final RepositoryType repositoryType) throws GitAPIException { final String programmingLanguage = programmingExercise.getProgrammingLanguage().toString().toLowerCase(Locale.ROOT); final ProjectType projectType = programmingExercise.getProjectType(); - final Path projectTypeTemplateDir = getTemplateDirectoryForRepositoryType(repositoryType); + final Path repositoryTypeTemplateDir = getTemplateDirectoryForRepositoryType(repositoryType); final VcsRepositoryUri repoUri = programmingExercise.getRepositoryURL(repositoryType); final Repository repo = gitService.getOrCheckoutRepository(repoUri, true); // Get path, files and prefix for the programming-language dependent files. They are copied first. final Path generalTemplatePath = ProgrammingExerciseService.getProgrammingLanguageTemplatePath(programmingExercise.getProgrammingLanguage()) - .resolve(projectTypeTemplateDir); + .resolve(repositoryTypeTemplateDir); Resource[] resources = resourceLoaderService.getFileResources(generalTemplatePath); - Path prefix = Path.of(programmingLanguage).resolve(projectTypeTemplateDir); + Path prefix = Path.of(programmingLanguage).resolve(repositoryTypeTemplateDir); Resource[] projectTypeResources = null; Path projectTypePrefix = null; @@ -149,8 +152,8 @@ private RepositoryResources getRepositoryResources(final ProgrammingExercise pro projectType); final String projectTypePath = projectType.name().toLowerCase(); final Path generalProjectTypePrefix = Path.of(programmingLanguage, projectTypePath); - final Path projectTypeSpecificPrefix = generalProjectTypePrefix.resolve(projectTypeTemplateDir); - final Path projectTypeTemplatePath = programmingLanguageProjectTypePath.resolve(projectTypeTemplateDir); + final Path projectTypeSpecificPrefix = generalProjectTypePrefix.resolve(repositoryTypeTemplateDir); + final Path projectTypeTemplatePath = programmingLanguageProjectTypePath.resolve(repositoryTypeTemplateDir); final Resource[] projectTypeSpecificResources = resourceLoaderService.getFileResources(projectTypeTemplatePath); @@ -165,7 +168,19 @@ private RepositoryResources getRepositoryResources(final ProgrammingExercise pro } } - return new RepositoryResources(repo, resources, prefix, projectTypeResources, projectTypePrefix); + Resource[] staticCodeAnalysisResources = null; + Path staticCodeAnalysisPrefix = null; + + if (programmingExercise.isStaticCodeAnalysisEnabled()) { + Path programmingLanguageStaticCodeAnalysisPath = ProgrammingExerciseService.getProgrammingLanguageTemplatePath(programmingExercise.getProgrammingLanguage()) + .resolve(STATIC_CODE_ANALYSIS_DIR); + final Path staticCodeAnalysisTemplatePath = programmingLanguageStaticCodeAnalysisPath.resolve(repositoryTypeTemplateDir); + + staticCodeAnalysisResources = resourceLoaderService.getFileResources(staticCodeAnalysisTemplatePath); + staticCodeAnalysisPrefix = Path.of(programmingLanguage, STATIC_CODE_ANALYSIS_DIR).resolve(repositoryTypeTemplateDir); + } + + return new RepositoryResources(repo, resources, prefix, projectTypeResources, projectTypePrefix, staticCodeAnalysisResources, staticCodeAnalysisPrefix); } private Path getTemplateDirectoryForRepositoryType(final RepositoryType repositoryType) { @@ -316,10 +331,13 @@ private void setupTemplateAndPush(final RepositoryResources repositoryResources, final Path repoLocalPath = getRepoAbsoluteLocalPath(repositoryResources.repository); fileService.copyResources(repositoryResources.resources, repositoryResources.prefix, repoLocalPath, true); - // Also copy project type specific files AFTERWARDS (so that they might overwrite the default files) + // Also copy project type and static code analysis specific files AFTERWARDS (so that they might overwrite the default files) if (repositoryResources.projectTypeResources != null) { fileService.copyResources(repositoryResources.projectTypeResources, repositoryResources.projectTypePrefix, repoLocalPath, true); } + if (repositoryResources.staticCodeAnalysisResources != null) { + fileService.copyResources(repositoryResources.staticCodeAnalysisResources, repositoryResources.staticCodeAnalysisPrefix, repoLocalPath, true); + } replacePlaceholders(programmingExercise, repositoryResources.repository); commitAndPushRepository(repositoryResources.repository, templateName + "-Template pushed by Artemis", true, user); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index e0486750422f..54126903a6d0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java @@ -53,7 +53,7 @@ public LocalCIProgrammingLanguageFeatureService() { programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, false, false, true, true, false, List.of(), false, true)); programmingLanguageFeatures.put(OCAML, new ProgrammingLanguageFeature(OCAML, false, false, false, false, true, List.of(), false, true)); - programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, true, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), false, true)); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserPolicy.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserPolicy.java index fa90c31c7c9d..43b263c7d282 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserPolicy.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserPolicy.java @@ -4,6 +4,8 @@ import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisTool; import de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception.UnsupportedToolException; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif.RuffCategorizer; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif.SarifParser; /** * Policy class for the parser strategies. @@ -27,7 +29,7 @@ public ParserStrategy configure(String fileName) { case CHECKSTYLE -> new CheckstyleParser(); case PMD -> new PMDParser(); case PMD_CPD -> new PMDCPDParser(); - // so far, we do not support swiftlint and gcc only SCA for Java + case RUFF -> new SarifParser(StaticCodeAnalysisTool.RUFF, new RuffCategorizer()); default -> throw new UnsupportedToolException("Tool " + tool + " is not supported"); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuffCategorizer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuffCategorizer.java new file mode 100644 index 000000000000..705611acd5ba --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuffCategorizer.java @@ -0,0 +1,15 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif; + +import java.util.Map; + +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.PropertyBag; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptor; + +public class RuffCategorizer implements RuleCategorizer { + + @Override + public String categorizeRule(ReportingDescriptor rule) { + Map properties = rule.getOptionalProperties().map(PropertyBag::additionalProperties).orElseGet(Map::of); + return properties.getOrDefault("kind", "Unknown").toString(); + } +} diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 78ae88017f7f..f36509f4be83 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -71,7 +71,7 @@ artemis: empty: default: "ubuntu:24.04" python: - default: "ls1tum/artemis-python-docker:v1.0.0" + default: "ls1tum/artemis-python-docker:v1.1.0" c: # possible overrides: gcc, fact default: "ls1tum/artemis-c-docker:v1.0.0" diff --git a/src/main/resources/templates/aeolus/python/default_static.sh b/src/main/resources/templates/aeolus/python/default_static.sh new file mode 100644 index 000000000000..a60edea08cb3 --- /dev/null +++ b/src/main/resources/templates/aeolus/python/default_static.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -e +export AEOLUS_INITIAL_DIRECTORY=${PWD} +static_code_analysis () { + echo '⚙️ executing static_code_analysis' + ruff check --config=ruff-student.toml --output-format=sarif --output-file=ruff.sarif --exit-zero "${studentParentWorkingDirectoryName}" +} + +build_and_test_the_code () { + echo '⚙️ executing build_and_test_the_code' + python3 -m compileall . -q || error=true + if [ ! $error ] + then + pytest --junitxml=test-reports/results.xml + fi +} + +main () { + if [[ "${1}" == "aeolus_sourcing" ]]; then + return 0 # just source to use the methods in the subshell, no execution + fi + local _script_name + _script_name=${BASH_SOURCE[0]:-$0} + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; static_code_analysis" + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; build_and_test_the_code" +} + +main "${@}" diff --git a/src/main/resources/templates/aeolus/python/default_static.yaml b/src/main/resources/templates/aeolus/python/default_static.yaml new file mode 100644 index 000000000000..b139cc84ffe8 --- /dev/null +++ b/src/main/resources/templates/aeolus/python/default_static.yaml @@ -0,0 +1,21 @@ +api: v0.0.1 +actions: + - name: static_code_analysis + script: ruff check --config=ruff-student.toml --output-format=sarif --output-file=ruff.sarif --exit-zero "${studentParentWorkingDirectoryName}" + results: + - name: ruff + path: ruff.sarif + type: sca + - name: build_and_test_the_code + script: |- + python3 -m compileall . -q || error=true + if [ ! $error ] + then + pytest --junitxml=test-reports/results.xml + fi + runAlways: false + results: + - name: junit_test-reports/*results.xml + path: test-reports/*results.xml + type: junit + before: true diff --git a/src/main/resources/templates/python/staticCodeAnalysis/test/ruff-student.toml b/src/main/resources/templates/python/staticCodeAnalysis/test/ruff-student.toml new file mode 100644 index 000000000000..9601c7eb09b0 --- /dev/null +++ b/src/main/resources/templates/python/staticCodeAnalysis/test/ruff-student.toml @@ -0,0 +1,35 @@ +[lint] +select = [ + # Pyflakes + "F", + # pycodestyle + "E", "W", + # isort + "I", + # flake8-async + "ASYNC", + # flake8-bugbear + "B", + # flake8-comprehensions + "C4", + # flake8-pie + "PIE", + # flake8-return + "RET", + # flake8-self + "SLF", + # flake8-simplify + "SIM", + # flake8-unused-arguments + "ARG", + # pandas-vet + "PD", + # Pylint + "PL", + # NumPy-specific rules + "NPY", + # refurb + "FURB", + # Ruff-specific rules + "RUF", +] diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java index a9d30a7d4b0b..e63f7263c27d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java @@ -74,6 +74,11 @@ void testSpotbugsParser() throws IOException { testParserWithFile("spotbugsXml.xml", "spotbugs.txt"); } + @Test + void testRuffParser() throws IOException { + testParserWithFile("ruff.sarif", "ruff.json"); + } + @Test void testParseInvalidXML() { assertThatCode(() -> testParserWithFileNamed("invalid_xml.xml", "pmd.xml", "invalid_xml.txt")).isInstanceOf(RuntimeException.class); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseFactory.java b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseFactory.java index bf4c207df1e6..2fc3bb8d8af5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseFactory.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseFactory.java @@ -341,6 +341,7 @@ private static StaticCodeAnalysisIssue generateStaticCodeAnalysisIssue(StaticCod case PMD_CPD -> "Copy/Paste Detection"; case SWIFTLINT -> "swiftLint"; // TODO: rene: set better value after categories are better defined case GCC -> "Memory"; + case RUFF -> "Pylint"; case OTHER -> "Other"; }; diff --git a/src/test/resources/test-data/static-code-analysis/expected/ruff.json b/src/test/resources/test-data/static-code-analysis/expected/ruff.json new file mode 100644 index 000000000000..b5d22a4de679 --- /dev/null +++ b/src/test/resources/test-data/static-code-analysis/expected/ruff.json @@ -0,0 +1,60 @@ +{ + "tool": "RUFF", + "issues": [ + { + "filePath": "/usr/local/src/exercise/policy.py", + "startLine": 1, + "endLine": 1, + "startColumn": 1, + "endColumn": 34, + "rule": "F403", + "category": "Pyflakes", + "message": "`from .sorting_algorithms import *` used; unable to detect undefined names", + "priority": "error" + }, + { + "filePath": "/usr/local/src/exercise/policy.py", + "startLine": 11, + "endLine": 11, + "startColumn": 40, + "endColumn": 42, + "rule": "PLR2004", + "category": "Pylint", + "message": "Magic value used in comparison, consider replacing `10` with a constant variable", + "priority": "error" + }, + { + "filePath": "/usr/local/src/exercise/policy.py", + "startLine": 13, + "endLine": 13, + "startColumn": 46, + "endColumn": 55, + "rule": "F405", + "category": "Pyflakes", + "message": "`MergeSort` may be undefined, or defined from star imports", + "priority": "error" + }, + { + "filePath": "/usr/local/src/exercise/policy.py", + "startLine": 16, + "endLine": 16, + "startColumn": 46, + "endColumn": 56, + "rule": "F405", + "category": "Pyflakes", + "message": "`BubbleSort` may be undefined, or defined from star imports", + "priority": "error" + }, + { + "filePath": "/usr/local/src/exercise/policy.py", + "startLine": 1, + "endLine": 1, + "startColumn": 1, + "endColumn": 34, + "rule": "X999", + "category": "Unknown", + "message": "Some unknown issue", + "priority": "error" + } + ] +} diff --git a/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif b/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif new file mode 100644 index 000000000000..363726778ea0 --- /dev/null +++ b/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif @@ -0,0 +1,195 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///usr/local/src/exercise/policy.py" + }, + "region": { + "endColumn": 34, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "`from .sorting_algorithms import *` used; unable to detect undefined names" + }, + "ruleId": "F403" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///usr/local/src/exercise/policy.py" + }, + "region": { + "endColumn": 42, + "endLine": 11, + "startColumn": 40, + "startLine": 11 + } + } + } + ], + "message": { + "text": "Magic value used in comparison, consider replacing `10` with a constant variable" + }, + "ruleId": "PLR2004" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///usr/local/src/exercise/policy.py" + }, + "region": { + "endColumn": 55, + "endLine": 13, + "startColumn": 46, + "startLine": 13 + } + } + } + ], + "message": { + "text": "`MergeSort` may be undefined, or defined from star imports" + }, + "ruleId": "F405" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///usr/local/src/exercise/policy.py" + }, + "region": { + "endColumn": 56, + "endLine": 16, + "startColumn": 46, + "startLine": 16 + } + } + } + ], + "message": { + "text": "`BubbleSort` may be undefined, or defined from star imports" + }, + "ruleId": "F405" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///usr/local/src/exercise/policy.py" + }, + "region": { + "endColumn": 34, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "Some unknown issue" + }, + "ruleId": "X999" + } + ], + "tool": { + "driver": { + "informationUri": "https://github.com/astral-sh/ruff", + "name": "ruff", + "rules": [ + { + "fullDescription": { + "text": "## What it does\nChecks for the use of wildcard imports.\n\n## Why is this bad?\nWildcard imports (e.g., `from module import *`) make it hard to determine\nwhich symbols are available in the current namespace, and from which module\nthey were imported. They're also discouraged by [PEP 8].\n\n## Example\n```python\nfrom math import *\n\n\ndef area(radius):\n return pi * radius**2\n```\n\nUse instead:\n```python\nfrom math import pi\n\n\ndef area(radius):\n return pi * radius**2\n```\n\n[PEP 8]: https://peps.python.org/pep-0008/#imports\n" + }, + "help": { + "text": "`from {name} import *` used; unable to detect undefined names" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/undefined-local-with-import-star", + "id": "F403", + "properties": { + "id": "F403", + "kind": "Pyflakes", + "name": "undefined-local-with-import-star", + "problem.severity": "error" + }, + "shortDescription": { + "text": "`from {name} import *` used; unable to detect undefined names" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for names that might be undefined, but may also be defined in a\nwildcard import.\n\n## Why is this bad?\nWildcard imports (e.g., `from module import *`) make it hard to determine\nwhich symbols are available in the current namespace. If a module contains\na wildcard import, and a name in the current namespace has not been\nexplicitly defined or imported, then it's unclear whether the name is\nundefined or was imported by the wildcard import.\n\nIf the name _is_ defined in via a wildcard import, that member should be\nimported explicitly to avoid confusion.\n\nIf the name is _not_ defined in a wildcard import, it should be defined or\nimported.\n\n## Example\n```python\nfrom math import *\n\n\ndef area(radius):\n return pi * radius**2\n```\n\nUse instead:\n```python\nfrom math import pi\n\n\ndef area(radius):\n return pi * radius**2\n```\n" + }, + "help": { + "text": "`{name}` may be undefined, or defined from star imports" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/undefined-local-with-import-star-usage", + "id": "F405", + "properties": { + "id": "F405", + "kind": "Pyflakes", + "name": "undefined-local-with-import-star-usage", + "problem.severity": "error" + }, + "shortDescription": { + "text": "`{name}` may be undefined, or defined from star imports" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for the use of unnamed numerical constants (\"magic\") values in\ncomparisons.\n\n## Why is this bad?\nThe use of \"magic\" values can make code harder to read and maintain, as\nreaders will have to infer the meaning of the value from the context.\nSuch values are discouraged by [PEP 8].\n\nFor convenience, this rule excludes a variety of common values from the\n\"magic\" value definition, such as `0`, `1`, `\"\"`, and `\"__main__\"`.\n\n## Example\n```python\ndef apply_discount(price: float) -> float:\n if price <= 100:\n return price / 2\n else:\n return price\n```\n\nUse instead:\n```python\nMAX_DISCOUNT = 100\n\n\ndef apply_discount(price: float) -> float:\n if price <= MAX_DISCOUNT:\n return price / 2\n else:\n return price\n```\n\n## Options\n- `lint.pylint.allow-magic-value-types`\n\n[PEP 8]: https://peps.python.org/pep-0008/#constants\n" + }, + "help": { + "text": "Magic value used in comparison, consider replacing `{value}` with a constant variable" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/magic-value-comparison", + "id": "PLR2004", + "properties": { + "id": "PLR2004", + "kind": "Pylint", + "name": "magic-value-comparison", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Magic value used in comparison, consider replacing `{value}` with a constant variable" + } + }, + { + "fullDescription": { + "text": "This is an issue which does not declare a kind in its property bag" + }, + "id": "X999", + "shortDescription": { + "text": "Some unknown issue" + } + } + ], + "version": "0.8.0" + } + } + } + ], + "version": "2.1.0" +}