From 366aae7569b7b0d9e6e0d9cacf2a57d7dbe2c54f Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Wed, 23 Oct 2024 17:44:45 +0200 Subject: [PATCH 01/23] Generate Sarif POJOs from JSON schema --- build.gradle | 15 + .../resources/json/sarif-schema-2.1.0.json | 3390 +++++++++++++++++ 2 files changed, 3405 insertions(+) create mode 100644 src/main/resources/json/sarif-schema-2.1.0.json diff --git a/build.gradle b/build.gradle index 52ed6f06d61f..cb28beb7c452 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ plugins { id "com.gorylenko.gradle-git-properties" version "2.4.2" id "org.owasp.dependencycheck" version "11.0.0" id "com.adarshr.test-logger" version "4.0.0" + id "org.jsonschema2pojo" version "1.2.2" } group = "de.tum.cit.aet.artemis" @@ -194,6 +195,12 @@ jacocoTestCoverageVerification { } check.dependsOn jacocoTestCoverageVerification +jsonSchema2Pojo { + targetDirectory = file(layout.buildDirectory.dir("generated/sources/js2p")) + useOptionalForGetters = true + inclusionLevel = "NON_EMPTY" +} + configurations { providedRuntime } @@ -643,6 +650,14 @@ checkstyle { maxErrors = 0 } +tasks.withType(Checkstyle).configureEach { + // Exclude generated sources + exclude { + it.file.toPath().startsWith(jsonSchema2Pojo.targetDirectory.toPath()) + } +} + + def isNonStable = { String version -> def stableKeyword = ["RELEASE", "FINAL", "GA"].any { it -> version.toUpperCase().contains(it) } def regex = /^[0-9,.v-]+(-r)?$/ diff --git a/src/main/resources/json/sarif-schema-2.1.0.json b/src/main/resources/json/sarif-schema-2.1.0.json new file mode 100644 index 000000000000..339e4e61862a --- /dev/null +++ b/src/main/resources/json/sarif-schema-2.1.0.json @@ -0,0 +1,3390 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema", + "id": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", + "description": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.", + "additionalProperties": false, + "type": "object", + "javaType": "de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif.SarifLog", + "properties": { + + "$schema": { + "description": "The URI of the JSON schema corresponding to the version.", + "type": "string", + "format": "uri" + }, + + "version": { + "description": "The SARIF format version of this log file.", + "enum": [ "2.1.0" ], + "type": "string" + }, + + "runs": { + "description": "The set of runs contained in this log file.", + "type": [ "array", "null" ], + "minItems": 0, + "uniqueItems": false, + "items": { + "$ref": "#/definitions/run" + } + }, + + "inlineExternalProperties": { + "description": "References to external property files that share data between runs.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/externalProperties" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the log file.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "version", "runs" ], + + "definitions": { + + "address": { + "description": "A physical or virtual address, or a range of addresses, in an 'addressable region' (memory or a binary file).", + "additionalProperties": false, + "type": "object", + "properties": { + + "absoluteAddress": { + "description": "The address expressed as a byte offset from the start of the addressable region.", + "type": "integer", + "minimum": -1, + "default": -1 + + }, + + "relativeAddress": { + "description": "The address expressed as a byte offset from the absolute address of the top-most parent object.", + "type": "integer" + + }, + + "length": { + "description": "The number of bytes in this range of addresses.", + "type": "integer" + }, + + "kind": { + "description": "An open-ended string that identifies the address kind. 'data', 'function', 'header','instruction', 'module', 'page', 'section', 'segment', 'stack', 'stackFrame', 'table' are well-known values.", + "type": "string" + }, + + "name": { + "description": "A name that is associated with the address, e.g., '.text'.", + "type": "string" + }, + + "fullyQualifiedName": { + "description": "A human-readable fully qualified name that is associated with the address.", + "type": "string" + }, + + "offsetFromParent": { + "description": "The byte offset of this address from the absolute or relative address of the parent object.", + "type": "integer" + }, + + "index": { + "description": "The index within run.addresses of the cached object for this address.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "parentIndex": { + "description": "The index within run.addresses of the parent object.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the address.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "artifact": { + "description": "A single artifact. In some cases, this artifact might be nested within another artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "description": { + "description": "A short description of the artifact.", + "$ref": "#/definitions/message" + }, + + "location": { + "description": "The location of the artifact.", + "$ref": "#/definitions/artifactLocation" + }, + + "parentIndex": { + "description": "Identifies the index of the immediate parent of the artifact, if this artifact is nested.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "offset": { + "description": "The offset in bytes of the artifact within its containing artifact.", + "type": "integer", + "minimum": 0 + }, + + "length": { + "description": "The length of the artifact in bytes.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "roles": { + "description": "The role or roles played by the artifact in the analysis.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "enum": [ + "analysisTarget", + "attachment", + "responseFile", + "resultFile", + "standardStream", + "tracedFile", + "unmodified", + "modified", + "added", + "deleted", + "renamed", + "uncontrolled", + "driver", + "extension", + "translation", + "taxonomy", + "policy", + "referencedOnCommandLine", + "memoryContents", + "directory", + "userSpecifiedConfiguration", + "toolSpecifiedConfiguration", + "debugOutputFile" + ], + "type": "string" + } + }, + + "mimeType": { + "description": "The MIME type (RFC 2045) of the artifact.", + "type": "string", + "pattern": "[^/]+/.+" + }, + + "contents": { + "description": "The contents of the artifact.", + "$ref": "#/definitions/artifactContent" + }, + + "encoding": { + "description": "Specifies the encoding for an artifact object that refers to a text file.", + "type": "string" + }, + + "sourceLanguage": { + "description": "Specifies the source language for any artifact object that refers to a text file that contains source code.", + "type": "string" + }, + + "hashes": { + "description": "A dictionary, each of whose keys is the name of a hash function and each of whose values is the hashed value of the artifact produced by the specified hash function.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "lastModifiedTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the artifact was most recently modified. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the artifact.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "artifactChange": { + "description": "A change to a single artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "artifactLocation": { + "description": "The location of the artifact to change.", + "$ref": "#/definitions/artifactLocation" + }, + + "replacements": { + "description": "An array of replacement objects, each of which represents the replacement of a single region in a single artifact specified by 'artifactLocation'.", + "type": "array", + "minItems": 1, + "uniqueItems": false, + "items": { + "$ref": "#/definitions/replacement" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the change.", + "$ref": "#/definitions/propertyBag" + } + + }, + + "required": [ "artifactLocation", "replacements" ] + }, + + "artifactContent": { + "description": "Represents the contents of an artifact.", + "type": "object", + "additionalProperties": false, + "properties": { + + "text": { + "description": "UTF-8-encoded content from a text artifact.", + "type": "string" + }, + + "binary": { + "description": "MIME Base64-encoded content from a binary artifact, or from a text artifact in its original encoding.", + "type": "string" + }, + + "rendered": { + "description": "An alternate rendered representation of the artifact (e.g., a decompiled representation of a binary region).", + "$ref": "#/definitions/multiformatMessageString" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the artifact content.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "artifactLocation": { + "description": "Specifies the location of an artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "uri": { + "description": "A string containing a valid relative or absolute URI.", + "type": "string", + "format": "uri-reference" + }, + + "uriBaseId": { + "description": "A string which indirectly specifies the absolute URI with respect to which a relative URI in the \"uri\" property is interpreted.", + "type": "string" + }, + + "index": { + "description": "The index within the run artifacts array of the artifact object associated with the artifact location.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "description": { + "description": "A short description of the artifact location.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the artifact location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "attachment": { + "description": "An artifact relevant to a result.", + "type": "object", + "additionalProperties": false, + "properties": { + + "description": { + "description": "A message describing the role played by the attachment.", + "$ref": "#/definitions/message" + }, + + "artifactLocation": { + "description": "The location of the attachment.", + "$ref": "#/definitions/artifactLocation" + }, + + "regions": { + "description": "An array of regions of interest within the attachment.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/region" + } + }, + + "rectangles": { + "description": "An array of rectangles specifying areas of interest within the image.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/rectangle" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the attachment.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "artifactLocation" ] + }, + + "codeFlow": { + "description": "A set of threadFlows which together describe a pattern of code execution relevant to detecting a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "message": { + "description": "A message relevant to the code flow.", + "$ref": "#/definitions/message" + }, + + "threadFlows": { + "description": "An array of one or more unique threadFlow objects, each of which describes the progress of a program through a thread of execution.", + "type": "array", + "minItems": 1, + "uniqueItems": false, + "items": { + "$ref": "#/definitions/threadFlow" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the code flow.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "threadFlows" ] + }, + + "configurationOverride": { + "description": "Information about how a specific rule or notification was reconfigured at runtime.", + "type": "object", + "additionalProperties": false, + "properties": { + + "configuration": { + "description": "Specifies how the rule or notification was configured during the scan.", + "$ref": "#/definitions/reportingConfiguration" + }, + + "descriptor": { + "description": "A reference used to locate the descriptor whose configuration was overridden.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the configuration override.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "configuration", "descriptor" ] + }, + + "conversion": { + "description": "Describes how a converter transformed the output of a static analysis tool from the analysis tool's native output format into the SARIF format.", + "additionalProperties": false, + "type": "object", + "properties": { + + "tool": { + "description": "A tool object that describes the converter.", + "$ref": "#/definitions/tool" + }, + + "invocation": { + "description": "An invocation object that describes the invocation of the converter.", + "$ref": "#/definitions/invocation" + }, + + "analysisToolLogFiles": { + "description": "The locations of the analysis tool's per-run log files.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the conversion.", + "$ref": "#/definitions/propertyBag" + } + + }, + + "required": [ "tool" ] + }, + + "edge": { + "description": "Represents a directed edge in a graph.", + "type": "object", + "additionalProperties": false, + "properties": { + + "id": { + "description": "A string that uniquely identifies the edge within its graph.", + "type": "string" + }, + + "label": { + "description": "A short description of the edge.", + "$ref": "#/definitions/message" + }, + + "sourceNodeId": { + "description": "Identifies the source node (the node at which the edge starts).", + "type": "string" + }, + + "targetNodeId": { + "description": "Identifies the target node (the node at which the edge ends).", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the edge.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "id", "sourceNodeId", "targetNodeId" ] + }, + + "edgeTraversal": { + "description": "Represents the traversal of a single edge during a graph traversal.", + "type": "object", + "additionalProperties": false, + "properties": { + + "edgeId": { + "description": "Identifies the edge being traversed.", + "type": "string" + }, + + "message": { + "description": "A message to display to the user as the edge is traversed.", + "$ref": "#/definitions/message" + }, + + "finalState": { + "description": "The values of relevant expressions after the edge has been traversed.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "stepOverEdgeCount": { + "description": "The number of edge traversals necessary to return from a nested graph.", + "type": "integer", + "minimum": 0 + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the edge traversal.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "edgeId" ] + }, + + "exception": { + "description": "Describes a runtime exception encountered during the execution of an analysis tool.", + "type": "object", + "additionalProperties": false, + "properties": { + + "kind": { + "type": "string", + "description": "A string that identifies the kind of exception, for example, the fully qualified type name of an object that was thrown, or the symbolic name of a signal." + }, + + "message": { + "description": "A message that describes the exception.", + "type": "string" + }, + + "stack": { + "description": "The sequence of function calls leading to the exception.", + "$ref": "#/definitions/stack" + }, + + "innerExceptions": { + "description": "An array of exception objects each of which is considered a cause of this exception.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/exception" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the exception.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "externalProperties": { + "description": "The top-level element of an external property file.", + "type": "object", + "additionalProperties": false, + "properties": { + + "schema": { + "description": "The URI of the JSON schema corresponding to the version of the external property file format.", + "type": "string", + "format": "uri" + }, + + "version": { + "description": "The SARIF format version of this external properties object.", + "enum": [ "2.1.0" ], + "type": "string" + }, + + "guid": { + "description": "A stable, unique identifier for this external properties object, in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "runGuid": { + "description": "A stable, unique identifier for the run associated with this external properties object, in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "conversion": { + "description": "A conversion object that will be merged with a separate run.", + "$ref": "#/definitions/conversion" + }, + + "graphs": { + "description": "An array of graph objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "default": [], + "uniqueItems": true, + "items": { + "$ref": "#/definitions/graph" + } + }, + + "externalizedProperties": { + "description": "Key/value pairs that provide additional information that will be merged with a separate run.", + "$ref": "#/definitions/propertyBag" + }, + + "artifacts": { + "description": "An array of artifact objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifact" + } + }, + + "invocations": { + "description": "Describes the invocation of the analysis tool that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/invocation" + } + }, + + "logicalLocations": { + "description": "An array of logical locations such as namespaces, types or functions that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/logicalLocation" + } + }, + + "threadFlowLocations": { + "description": "An array of threadFlowLocation objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/threadFlowLocation" + } + }, + + "results": { + "description": "An array of result objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/result" + } + }, + + "taxonomies": { + "description": "Tool taxonomies that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "driver": { + "description": "The analysis tool object that will be merged with a separate run.", + "$ref": "#/definitions/toolComponent" + }, + + "extensions": { + "description": "Tool extensions that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "policies": { + "description": "Tool policies that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "translations": { + "description": "Tool translations that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "addresses": { + "description": "Addresses that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/address" + } + }, + + "webRequests": { + "description": "Requests that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webRequest" + } + }, + + "webResponses": { + "description": "Responses that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webResponse" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the external properties.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "externalPropertyFileReference": { + "description": "Contains information that enables a SARIF consumer to locate the external property file that contains the value of an externalized property associated with the run.", + "type": "object", + "additionalProperties": false, + "properties": { + + "location": { + "description": "The location of the external property file.", + "$ref": "#/definitions/artifactLocation" + }, + + "guid": { + "description": "A stable, unique identifier for the external property file in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "itemCount": { + "description": "A non-negative integer specifying the number of items contained in the external property file.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the external property file.", + "$ref": "#/definitions/propertyBag" + } + }, + "anyOf": [ + { "required": [ "location" ] }, + { "required": [ "guid" ] } + ] + }, + + "externalPropertyFileReferences": { + "description": "References to external property files that should be inlined with the content of a root log file.", + "additionalProperties": false, + "type": "object", + "properties": { + + "conversion": { + "description": "An external property file containing a run.conversion object to be merged with the root log file.", + "$ref": "#/definitions/externalPropertyFileReference" + }, + + "graphs": { + "description": "An array of external property files containing a run.graphs object to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "externalizedProperties": { + "description": "An external property file containing a run.properties object to be merged with the root log file.", + "$ref": "#/definitions/externalPropertyFileReference" + }, + + "artifacts": { + "description": "An array of external property files containing run.artifacts arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "invocations": { + "description": "An array of external property files containing run.invocations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "logicalLocations": { + "description": "An array of external property files containing run.logicalLocations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "threadFlowLocations": { + "description": "An array of external property files containing run.threadFlowLocations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "results": { + "description": "An array of external property files containing run.results arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "taxonomies": { + "description": "An array of external property files containing run.taxonomies arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "addresses": { + "description": "An array of external property files containing run.addresses arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "driver": { + "description": "An external property file containing a run.driver object to be merged with the root log file.", + "$ref": "#/definitions/externalPropertyFileReference" + }, + + "extensions": { + "description": "An array of external property files containing run.extensions arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "policies": { + "description": "An array of external property files containing run.policies arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "translations": { + "description": "An array of external property files containing run.translations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "webRequests": { + "description": "An array of external property files containing run.requests arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "webResponses": { + "description": "An array of external property files containing run.responses arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the external property files.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "fix": { + "description": "A proposed fix for the problem represented by a result object. A fix specifies a set of artifacts to modify. For each artifact, it specifies a set of bytes to remove, and provides a set of new bytes to replace them.", + "additionalProperties": false, + "type": "object", + "properties": { + + "description": { + "description": "A message that describes the proposed fix, enabling viewers to present the proposed change to an end user.", + "$ref": "#/definitions/message" + }, + + "artifactChanges": { + "description": "One or more artifact changes that comprise a fix for a result.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifactChange" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the fix.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "artifactChanges" ] + }, + + "graph": { + "description": "A network of nodes and directed edges that describes some aspect of the structure of the code (for example, a call graph).", + "type": "object", + "additionalProperties": false, + "properties": { + + "description": { + "description": "A description of the graph.", + "$ref": "#/definitions/message" + }, + + "nodes": { + "description": "An array of node objects representing the nodes of the graph.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/node" + } + }, + + "edges": { + "description": "An array of edge objects representing the edges of the graph.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/edge" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the graph.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "graphTraversal": { + "description": "Represents a path through a graph.", + "type": "object", + "additionalProperties": false, + "properties": { + + "runGraphIndex": { + "description": "The index within the run.graphs to be associated with the result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "resultGraphIndex": { + "description": "The index within the result.graphs to be associated with the result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "description": { + "description": "A description of this graph traversal.", + "$ref": "#/definitions/message" + }, + + "initialState": { + "description": "Values of relevant expressions at the start of the graph traversal that may change during graph traversal.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "immutableState": { + "description": "Values of relevant expressions at the start of the graph traversal that remain constant for the graph traversal.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "edgeTraversals": { + "description": "The sequences of edges traversed by this graph traversal.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/edgeTraversal" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the graph traversal.", + "$ref": "#/definitions/propertyBag" + } + }, + "oneOf": [ + { "required": [ "runGraphIndex" ] }, + { "required": [ "resultGraphIndex" ] } + ] + }, + + "invocation": { + "description": "The runtime environment of the analysis tool run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "commandLine": { + "description": "The command line used to invoke the tool.", + "type": "string" + }, + + "arguments": { + "description": "An array of strings, containing in order the command line arguments passed to the tool from the operating system.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "items": { + "type": "string" + } + }, + + "responseFiles": { + "description": "The locations of any response files specified on the tool's command line.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "startTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the invocation started. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "endTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the invocation ended. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "exitCode": { + "description": "The process exit code.", + "type": "integer" + }, + + "ruleConfigurationOverrides": { + "description": "An array of configurationOverride objects that describe rules related runtime overrides.", + "type": "array", + "minItems": 0, + "default": [], + "uniqueItems": true, + "items": { + "$ref": "#/definitions/configurationOverride" + } + }, + + "notificationConfigurationOverrides": { + "description": "An array of configurationOverride objects that describe notifications related runtime overrides.", + "type": "array", + "minItems": 0, + "default": [], + "uniqueItems": true, + "items": { + "$ref": "#/definitions/configurationOverride" + } + }, + + "toolExecutionNotifications": { + "description": "A list of runtime conditions detected by the tool during the analysis.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/notification" + } + }, + + "toolConfigurationNotifications": { + "description": "A list of conditions detected by the tool that are relevant to the tool's configuration.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/notification" + } + }, + + "exitCodeDescription": { + "description": "The reason for the process exit.", + "type": "string" + }, + + "exitSignalName": { + "description": "The name of the signal that caused the process to exit.", + "type": "string" + }, + + "exitSignalNumber": { + "description": "The numeric value of the signal that caused the process to exit.", + "type": "integer" + }, + + "processStartFailureMessage": { + "description": "The reason given by the operating system that the process failed to start.", + "type": "string" + }, + + "executionSuccessful": { + "description": "Specifies whether the tool's execution completed successfully.", + "type": "boolean" + }, + + "machine": { + "description": "The machine on which the invocation occurred.", + "type": "string" + }, + + "account": { + "description": "The account under which the invocation occurred.", + "type": "string" + }, + + "processId": { + "description": "The id of the process in which the invocation occurred.", + "type": "integer" + }, + + "executableLocation": { + "description": "An absolute URI specifying the location of the executable that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "workingDirectory": { + "description": "The working directory for the invocation.", + "$ref": "#/definitions/artifactLocation" + }, + + "environmentVariables": { + "description": "The environment variables associated with the analysis tool process, expressed as key/value pairs.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "stdin": { + "description": "A file containing the standard input stream to the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "stdout": { + "description": "A file containing the standard output stream from the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "stderr": { + "description": "A file containing the standard error stream from the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "stdoutStderr": { + "description": "A file containing the interleaved standard output and standard error stream from the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the invocation.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "executionSuccessful" ] + }, + + "location": { + "description": "A location within a programming artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "id": { + "description": "Value that distinguishes this location from all other locations within a single result object.", + "type": "integer", + "minimum": -1, + "default": -1 + }, + + "physicalLocation": { + "description": "Identifies the artifact and region.", + "$ref": "#/definitions/physicalLocation" + }, + + "logicalLocations": { + "description": "The logical locations associated with the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/logicalLocation" + } + }, + + "message": { + "description": "A message relevant to the location.", + "$ref": "#/definitions/message" + }, + + "annotations": { + "description": "A set of regions relevant to the location.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/region" + } + }, + + "relationships": { + "description": "An array of objects that describe relationships between this location and others.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/locationRelationship" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "locationRelationship": { + "description": "Information about the relation of one location to another.", + "type": "object", + "additionalProperties": false, + "properties": { + + "target": { + "description": "A reference to the related location.", + "type": "integer", + "minimum": 0 + }, + + "kinds": { + "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'includes', 'isIncludedBy' and 'relevant'.", + "type": "array", + "default": [ "relevant" ], + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "description": { + "description": "A description of the location relationship.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the location relationship.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "target" ] + }, + + "logicalLocation": { + "description": "A logical location of a construct that produced a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "name": { + "description": "Identifies the construct in which the result occurred. For example, this property might contain the name of a class or a method.", + "type": "string" + }, + + "index": { + "description": "The index within the logical locations array.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "fullyQualifiedName": { + "description": "The human-readable fully qualified name of the logical location.", + "type": "string" + }, + + "decoratedName": { + "description": "The machine-readable name for the logical location, such as a mangled function name provided by a C++ compiler that encodes calling convention, return type and other details along with the function name.", + "type": "string" + }, + + "parentIndex": { + "description": "Identifies the index of the immediate parent of the construct in which the result was detected. For example, this property might point to a logical location that represents the namespace that holds a type.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "kind": { + "description": "The type of construct this logical location component refers to. Should be one of 'function', 'member', 'module', 'namespace', 'parameter', 'resource', 'returnType', 'type', 'variable', 'object', 'array', 'property', 'value', 'element', 'text', 'attribute', 'comment', 'declaration', 'dtd' or 'processingInstruction', if any of those accurately describe the construct.", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the logical location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "message": { + "description": "Encapsulates a message intended to be read by the end user.", + "type": "object", + "additionalProperties": false, + + "properties": { + + "text": { + "description": "A plain text message string.", + "type": "string" + }, + + "markdown": { + "description": "A Markdown message string.", + "type": "string" + }, + + "id": { + "description": "The identifier for this message.", + "type": "string" + }, + + "arguments": { + "description": "An array of strings to substitute into the message string.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "type": "string" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the message.", + "$ref": "#/definitions/propertyBag" + } + }, + "anyOf": [ + { "required": [ "text" ] }, + { "required": [ "id" ] } + ] + }, + + "multiformatMessageString": { + "description": "A message string or message format string rendered in multiple formats.", + "type": "object", + "additionalProperties": false, + + "properties": { + + "text": { + "description": "A plain text message string or format string.", + "type": "string" + }, + + "markdown": { + "description": "A Markdown message string or format string.", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the message.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "text" ] + }, + + "node": { + "description": "Represents a node in a graph.", + "type": "object", + "additionalProperties": false, + + "properties": { + + "id": { + "description": "A string that uniquely identifies the node within its graph.", + "type": "string" + }, + + "label": { + "description": "A short description of the node.", + "$ref": "#/definitions/message" + }, + + "location": { + "description": "A code location associated with the node.", + "$ref": "#/definitions/location" + }, + + "children": { + "description": "Array of child nodes.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/node" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the node.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "id" ] + }, + + "notification": { + "description": "Describes a condition relevant to the tool itself, as opposed to being relevant to a target being analyzed by the tool.", + "type": "object", + "additionalProperties": false, + "properties": { + + "locations": { + "description": "The locations relevant to this notification.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/location" + } + }, + + "message": { + "description": "A message that describes the condition that was encountered.", + "$ref": "#/definitions/message" + }, + + "level": { + "description": "A value specifying the severity level of the notification.", + "default": "warning", + "enum": [ "none", "note", "warning", "error" ], + "type": "string" + }, + + "threadId": { + "description": "The thread identifier of the code that generated the notification.", + "type": "integer" + }, + + "timeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the analysis tool generated the notification.", + "type": "string", + "format": "date-time" + }, + + "exception": { + "description": "The runtime exception, if any, relevant to this notification.", + "$ref": "#/definitions/exception" + }, + + "descriptor": { + "description": "A reference used to locate the descriptor relevant to this notification.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "associatedRule": { + "description": "A reference used to locate the rule descriptor associated with this notification.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the notification.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "message" ] + }, + + "physicalLocation": { + "description": "A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "address": { + "description": "The address of the location.", + "$ref": "#/definitions/address" + }, + + "artifactLocation": { + "description": "The location of the artifact.", + "$ref": "#/definitions/artifactLocation" + }, + + "region": { + "description": "Specifies a portion of the artifact.", + "$ref": "#/definitions/region" + }, + + "contextRegion": { + "description": "Specifies a portion of the artifact that encloses the region. Allows a viewer to display additional context around the region.", + "$ref": "#/definitions/region" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the physical location.", + "$ref": "#/definitions/propertyBag" + } + }, + + "anyOf": [ + { + "required": [ "address" ] + }, + { + "required": [ "artifactLocation" ] + } + ] + }, + + "propertyBag": { + "description": "Key/value pairs that provide additional information about the object.", + "type": "object", + "additionalProperties": true, + "properties": { + "tags": { + + "description": "A set of distinct strings that provide additional information.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + } + } + }, + + "rectangle": { + "description": "An area within an image.", + "additionalProperties": false, + "type": "object", + "properties": { + + "top": { + "description": "The Y coordinate of the top edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "left": { + "description": "The X coordinate of the left edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "bottom": { + "description": "The Y coordinate of the bottom edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "right": { + "description": "The X coordinate of the right edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "message": { + "description": "A message relevant to the rectangle.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the rectangle.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "region": { + "description": "A region within an artifact where a result was detected.", + "additionalProperties": false, + "type": "object", + "properties": { + + "startLine": { + "description": "The line number of the first character in the region.", + "type": "integer", + "minimum": 1 + }, + + "startColumn": { + "description": "The column number of the first character in the region.", + "type": "integer", + "minimum": 1 + }, + + "endLine": { + "description": "The line number of the last character in the region.", + "type": "integer", + "minimum": 1 + }, + + "endColumn": { + "description": "The column number of the character following the end of the region.", + "type": "integer", + "minimum": 1 + }, + + "charOffset": { + "description": "The zero-based offset from the beginning of the artifact of the first character in the region.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "charLength": { + "description": "The length of the region in characters.", + "type": "integer", + "minimum": 0 + }, + + "byteOffset": { + "description": "The zero-based offset from the beginning of the artifact of the first byte in the region.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "byteLength": { + "description": "The length of the region in bytes.", + "type": "integer", + "minimum": 0 + }, + + "snippet": { + "description": "The portion of the artifact contents within the specified region.", + "$ref": "#/definitions/artifactContent" + }, + + "message": { + "description": "A message relevant to the region.", + "$ref": "#/definitions/message" + }, + + "sourceLanguage": { + "description": "Specifies the source language, if any, of the portion of the artifact specified by the region object.", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the region.", + "$ref": "#/definitions/propertyBag" + } + }, + + "anyOf": [ + { "required": [ "startLine" ] }, + { "required": [ "charOffset" ] }, + { "required": [ "byteOffset" ] } + ] + }, + + "replacement": { + "description": "The replacement of a single region of an artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "deletedRegion": { + "description": "The region of the artifact to delete.", + "$ref": "#/definitions/region" + }, + + "insertedContent": { + "description": "The content to insert at the location specified by the 'deletedRegion' property.", + "$ref": "#/definitions/artifactContent" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the replacement.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "deletedRegion" ] + }, + + "reportingDescriptor": { + "description": "Metadata that describes a specific report produced by the tool, as part of the analysis it provides or its runtime reporting.", + "additionalProperties": false, + "type": "object", + "properties": { + + "id": { + "description": "A stable, opaque identifier for the report.", + "type": "string" + }, + + "deprecatedIds": { + "description": "An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "guid": { + "description": "A unique identifier for the reporting descriptor in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "deprecatedGuids": { + "description": "An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + } + }, + + "name": { + "description": "A report identifier that is understandable to an end user.", + "type": "string" + }, + + "deprecatedNames": { + "description": "An array of readable identifiers by which this report was known in some previous version of the analysis tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "shortDescription": { + "description": "A concise description of the report. Should be a single sentence that is understandable when visible space is limited to a single line of text.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullDescription": { + "description": "A description of the report. Should, as far as possible, provide details sufficient to enable resolution of any problem indicated by the result.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "messageStrings": { + "description": "A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "defaultConfiguration": { + "description": "Default reporting configuration information.", + "$ref": "#/definitions/reportingConfiguration" + }, + + "helpUri": { + "description": "A URI where the primary documentation for the report can be found.", + "type": "string", + "format": "uri" + }, + + "help": { + "description": "Provides the primary documentation for the report, useful when there is no online documentation.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "relationships": { + "description": "An array of objects that describe relationships between this reporting descriptor and others.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/reportingDescriptorRelationship" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the report.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "id" ] + }, + + "reportingConfiguration": { + "description": "Information about a rule or notification that can be configured at runtime.", + "type": "object", + "additionalProperties": false, + "properties": { + + "enabled": { + "description": "Specifies whether the report may be produced during the scan.", + "type": "boolean", + "default": true + }, + + "level": { + "description": "Specifies the failure level for the report.", + "default": "warning", + "enum": [ "none", "note", "warning", "error" ], + "type": "string" + }, + + "rank": { + "description": "Specifies the relative priority of the report. Used for analysis output only.", + "type": "number", + "default": -1.0, + "minimum": -1.0, + "maximum": 100.0 + }, + + "parameters": { + "description": "Contains configuration information specific to a report.", + "$ref": "#/definitions/propertyBag" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the reporting configuration.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "reportingDescriptorReference": { + "description": "Information about how to locate a relevant reporting descriptor.", + "type": "object", + "additionalProperties": false, + "properties": { + + "id": { + "description": "The id of the descriptor.", + "type": "string" + }, + + "index": { + "description": "The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "guid": { + "description": "A guid that uniquely identifies the descriptor.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "toolComponent": { + "description": "A reference used to locate the toolComponent associated with the descriptor.", + "$ref": "#/definitions/toolComponentReference" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", + "$ref": "#/definitions/propertyBag" + } + }, + "anyOf": [ + { "required": [ "index" ] }, + { "required": [ "guid" ] }, + { "required": [ "id" ] } + ] + }, + + "reportingDescriptorRelationship": { + "description": "Information about the relation of one reporting descriptor to another.", + "type": "object", + "additionalProperties": false, + "properties": { + + "target": { + "description": "A reference to the related reporting descriptor.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "kinds": { + "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'canPrecede', 'canFollow', 'willPrecede', 'willFollow', 'superset', 'subset', 'equal', 'disjoint', 'relevant', and 'incomparable'.", + "type": "array", + "default": [ "relevant" ], + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "description": { + "description": "A description of the reporting descriptor relationship.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "target" ] + }, + + "result": { + "description": "A result produced by an analysis tool.", + "additionalProperties": false, + "type": "object", + "properties": { + + "ruleId": { + "description": "The stable, unique identifier of the rule, if any, to which this result is relevant.", + "type": "string" + }, + + "ruleIndex": { + "description": "The index within the tool component rules array of the rule object associated with this result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "rule": { + "description": "A reference used to locate the rule descriptor relevant to this result.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "kind": { + "description": "A value that categorizes results by evaluation state.", + "default": "fail", + "enum": [ "notApplicable", "pass", "fail", "review", "open", "informational" ], + "type": "string" + }, + + "level": { + "description": "A value specifying the severity level of the result.", + "default": "warning", + "enum": [ "none", "note", "warning", "error" ], + "type": "string" + }, + + "message": { + "description": "A message that describes the result. The first sentence of the message only will be displayed when visible space is limited.", + "$ref": "#/definitions/message" + }, + + "analysisTarget": { + "description": "Identifies the artifact that the analysis tool was instructed to scan. This need not be the same as the artifact where the result actually occurred.", + "$ref": "#/definitions/artifactLocation" + }, + + "locations": { + "description": "The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every specified location.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/location" + } + }, + + "guid": { + "description": "A stable, unique identifier for the result in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "correlationGuid": { + "description": "A stable, unique identifier for the equivalence class of logically identical results to which this result belongs, in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "occurrenceCount": { + "description": "A positive integer specifying the number of times this logically unique result was observed in this run.", + "type": "integer", + "minimum": 1 + }, + + "partialFingerprints": { + "description": "A set of strings that contribute to the stable, unique identity of the result.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "fingerprints": { + "description": "A set of strings each of which individually defines a stable, unique identity for the result.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "stacks": { + "description": "An array of 'stack' objects relevant to the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/stack" + } + }, + + "codeFlows": { + "description": "An array of 'codeFlow' objects relevant to the result.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/codeFlow" + } + }, + + "graphs": { + "description": "An array of zero or more unique graph objects associated with the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/graph" + } + }, + + "graphTraversals": { + "description": "An array of one or more unique 'graphTraversal' objects.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/graphTraversal" + } + }, + + "relatedLocations": { + "description": "A set of locations relevant to this result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/location" + } + }, + + "suppressions": { + "description": "A set of suppressions relevant to this result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/suppression" + } + }, + + "baselineState": { + "description": "The state of a result relative to a baseline of a previous run.", + "enum": [ + "new", + "unchanged", + "updated", + "absent" + ], + "type": "string" + }, + + "rank": { + "description": "A number representing the priority or importance of the result.", + "type": "number", + "default": -1.0, + "minimum": -1.0, + "maximum": 100.0 + }, + + "attachments": { + "description": "A set of artifacts relevant to the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/attachment" + } + }, + + "hostedViewerUri": { + "description": "An absolute URI at which the result can be viewed.", + "type": "string", + "format": "uri" + }, + + "workItemUris": { + "description": "The URIs of the work items associated with this result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string", + "format": "uri" + } + }, + + "provenance": { + "description": "Information about how and when the result was detected.", + "$ref": "#/definitions/resultProvenance" + }, + + "fixes": { + "description": "An array of 'fix' objects, each of which represents a proposed fix to the problem indicated by the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/fix" + } + }, + + "taxa": { + "description": "An array of references to taxonomy reporting descriptors that are applicable to the result.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/reportingDescriptorReference" + } + }, + + "webRequest": { + "description": "A web request associated with this result.", + "$ref": "#/definitions/webRequest" + }, + + "webResponse": { + "description": "A web response associated with this result.", + "$ref": "#/definitions/webResponse" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the result.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "message" ] + }, + + "resultProvenance": { + "description": "Contains information about how and when a result was detected.", + "additionalProperties": false, + "type": "object", + "properties": { + + "firstDetectionTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the result was first detected. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "lastDetectionTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the result was most recently detected. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "firstDetectionRunGuid": { + "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was first detected.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "lastDetectionRunGuid": { + "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was most recently detected.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "invocationIndex": { + "description": "The index within the run.invocations array of the invocation object which describes the tool invocation that detected the result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "conversionSources": { + "description": "An array of physicalLocation objects which specify the portions of an analysis tool's output that a converter transformed into the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/physicalLocation" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the result.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "run": { + "description": "Describes a single run of an analysis tool, and contains the reported output of that run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "tool": { + "description": "Information about the tool or tool pipeline that generated the results in this run. A run can only contain results produced by a single tool or tool pipeline. A run can aggregate results from multiple log files, as long as context around the tool run (tool command-line arguments and the like) is identical for all aggregated files.", + "$ref": "#/definitions/tool" + }, + + "invocations": { + "description": "Describes the invocation of the analysis tool.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/invocation" + } + }, + + "conversion": { + "description": "A conversion object that describes how a converter transformed an analysis tool's native reporting format into the SARIF format.", + "$ref": "#/definitions/conversion" + }, + + "language": { + "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase culture code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", + "type": "string", + "default": "en-US", + "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" + }, + + "versionControlProvenance": { + "description": "Specifies the revision in version control of the artifacts that were scanned.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/versionControlDetails" + } + }, + + "originalUriBaseIds": { + "description": "The artifact location specified by each uriBaseId symbol on the machine where the tool originally ran.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "artifacts": { + "description": "An array of artifact objects relevant to the run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifact" + } + }, + + "logicalLocations": { + "description": "An array of logical locations such as namespaces, types or functions.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/logicalLocation" + } + }, + + "graphs": { + "description": "An array of zero or more unique graph objects associated with the run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/graph" + } + }, + + "results": { + "description": "The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be empty) if a log file represents an actual scan.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "items": { + "$ref": "#/definitions/result" + } + }, + + "automationDetails": { + "description": "Automation details that describe this run.", + "$ref": "#/definitions/runAutomationDetails" + }, + + "runAggregates": { + "description": "Automation details that describe the aggregate of runs to which this run belongs.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/runAutomationDetails" + } + }, + + "baselineGuid": { + "description": "The 'guid' property of a previous SARIF 'run' that comprises the baseline that was used to compute result 'baselineState' properties for the run.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "redactionTokens": { + "description": "An array of strings used to replace sensitive information in a redaction-aware property.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + }, + + "defaultEncoding": { + "description": "Specifies the default encoding for any artifact object that refers to a text file.", + "type": "string" + }, + + "defaultSourceLanguage": { + "description": "Specifies the default source language for any artifact object that refers to a text file that contains source code.", + "type": "string" + }, + + "newlineSequences": { + "description": "An ordered list of character sequences that were treated as line breaks when computing region information for the run.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "default": [ "\r\n", "\n" ], + "items": { + "type": "string" + } + }, + + "columnKind": { + "description": "Specifies the unit in which the tool measures columns.", + "enum": [ "utf16CodeUnits", "unicodeCodePoints" ], + "type": "string" + }, + + "externalPropertyFileReferences": { + "description": "References to external property files that should be inlined with the content of a root log file.", + "$ref": "#/definitions/externalPropertyFileReferences" + }, + + "threadFlowLocations": { + "description": "An array of threadFlowLocation objects cached at run level.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/threadFlowLocation" + } + }, + + "taxonomies": { + "description": "An array of toolComponent objects relevant to a taxonomy in which results are categorized.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "addresses": { + "description": "Addresses associated with this run instance, if any.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/address" + } + }, + + "translations": { + "description": "The set of available translations of the localized data provided by the tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "policies": { + "description": "Contains configurations that may potentially override both reportingDescriptor.defaultConfiguration (the tool's default severities) and invocation.configurationOverrides (severities established at run-time from the command line).", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "webRequests": { + "description": "An array of request objects cached at run level.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webRequest" + } + }, + + "webResponses": { + "description": "An array of response objects cached at run level.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webResponse" + } + }, + + "specialLocations": { + "description": "A specialLocations object that defines locations of special significance to SARIF consumers.", + "$ref": "#/definitions/specialLocations" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the run.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "tool" ] + }, + + "runAutomationDetails": { + "description": "Information that describes a run's identity and role within an engineering system process.", + "additionalProperties": false, + "type": "object", + "properties": { + + "description": { + "description": "A description of the identity and role played within the engineering system by this object's containing run object.", + "$ref": "#/definitions/message" + }, + + "id": { + "description": "A hierarchical string that uniquely identifies this object's containing run object.", + "type": "string" + }, + + "guid": { + "description": "A stable, unique identifier for this object's containing run object in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "correlationGuid": { + "description": "A stable, unique identifier for the equivalence class of runs to which this object's containing run object belongs in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the run automation details.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "specialLocations": { + "description": "Defines locations of special significance to SARIF consumers.", + "type": "object", + "additionalProperties": false, + "properties": { + + "displayBase": { + "description": "Provides a suggestion to SARIF consumers to display file paths relative to the specified location.", + "$ref": "#/definitions/artifactLocation" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the special locations.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "stack": { + "description": "A call stack that is relevant to a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "message": { + "description": "A message relevant to this call stack.", + "$ref": "#/definitions/message" + }, + + "frames": { + "description": "An array of stack frames that represents a sequence of calls, rendered in reverse chronological order, that comprise the call stack.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "items": { + "$ref": "#/definitions/stackFrame" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the stack.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "frames" ] + }, + + "stackFrame": { + "description": "A function call within a stack trace.", + "additionalProperties": false, + "type": "object", + "properties": { + + "location": { + "description": "The location to which this stack frame refers.", + "$ref": "#/definitions/location" + }, + + "module": { + "description": "The name of the module that contains the code of this stack frame.", + "type": "string" + }, + + "threadId": { + "description": "The thread identifier of the stack frame.", + "type": "integer" + }, + + "parameters": { + "description": "The parameters of the call that is executing.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "type": "string", + "default": [] + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the stack frame.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "suppression": { + "description": "A suppression that is relevant to a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "guid": { + "description": "A stable, unique identifier for the suprression in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "kind": { + "description": "A string that indicates where the suppression is persisted.", + "enum": [ + "inSource", + "external" + ], + "type": "string" + }, + + "status": { + "description": "A string that indicates the review status of the suppression.", + "enum": [ + "accepted", + "underReview", + "rejected" + ], + "type": "string" + }, + + "justification": { + "description": "A string representing the justification for the suppression.", + "type": "string" + }, + + "location": { + "description": "Identifies the location associated with the suppression.", + "$ref": "#/definitions/location" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the suppression.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "kind" ] + }, + + "threadFlow": { + "description": "Describes a sequence of code locations that specify a path through a single thread of execution such as an operating system or fiber.", + "type": "object", + "additionalProperties": false, + "properties": { + + "id": { + "description": "An string that uniquely identifies the threadFlow within the codeFlow in which it occurs.", + "type": "string" + }, + + "message": { + "description": "A message relevant to the thread flow.", + "$ref": "#/definitions/message" + }, + + + "initialState": { + "description": "Values of relevant expressions at the start of the thread flow that may change during thread flow execution.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "immutableState": { + "description": "Values of relevant expressions at the start of the thread flow that remain constant.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "locations": { + "description": "A temporally ordered array of 'threadFlowLocation' objects, each of which describes a location visited by the tool while producing the result.", + "type": "array", + "minItems": 1, + "uniqueItems": false, + "items": { + "$ref": "#/definitions/threadFlowLocation" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the thread flow.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "locations" ] + }, + + "threadFlowLocation": { + "description": "A location visited by an analysis tool while simulating or monitoring the execution of a program.", + "additionalProperties": false, + "type": "object", + "properties": { + + "index": { + "description": "The index within the run threadFlowLocations array.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "location": { + "description": "The code location.", + "$ref": "#/definitions/location" + }, + + "stack": { + "description": "The call stack leading to this location.", + "$ref": "#/definitions/stack" + }, + + "kinds": { + "description": "A set of distinct strings that categorize the thread flow location. Well-known kinds include 'acquire', 'release', 'enter', 'exit', 'call', 'return', 'branch', 'implicit', 'false', 'true', 'caution', 'danger', 'unknown', 'unreachable', 'taint', 'function', 'handler', 'lock', 'memory', 'resource', 'scope' and 'value'.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + }, + + "taxa": { + "description": "An array of references to rule or taxonomy reporting descriptors that are applicable to the thread flow location.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/reportingDescriptorReference" + } + }, + + "module": { + "description": "The name of the module that contains the code that is executing.", + "type": "string" + }, + + "state": { + "description": "A dictionary, each of whose keys specifies a variable or expression, the associated value of which represents the variable or expression value. For an annotation of kind 'continuation', for example, this dictionary might hold the current assumed values of a set of global variables.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "nestingLevel": { + "description": "An integer representing a containment hierarchy within the thread flow.", + "type": "integer", + "minimum": 0 + }, + + "executionOrder": { + "description": "An integer representing the temporal order in which execution reached this location.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "executionTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which this location was executed.", + "type": "string", + "format": "date-time" + }, + + "importance": { + "description": "Specifies the importance of this location in understanding the code flow in which it occurs. The order from most to least important is \"essential\", \"important\", \"unimportant\". Default: \"important\".", + "enum": [ "important", "essential", "unimportant" ], + "default": "important", + "type": "string" + }, + + "webRequest": { + "description": "A web request associated with this thread flow location.", + "$ref": "#/definitions/webRequest" + }, + + "webResponse": { + "description": "A web response associated with this thread flow location.", + "$ref": "#/definitions/webResponse" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the threadflow location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "tool": { + "description": "The analysis tool that was run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "driver": { + "description": "The analysis tool that was run.", + "$ref": "#/definitions/toolComponent" + }, + + "extensions": { + "description": "Tool extensions that contributed to or reconfigured the analysis tool that was run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the tool.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "driver" ] + }, + + "toolComponent": { + "description": "A component, such as a plug-in or the driver, of the analysis tool that was run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "guid": { + "description": "A unique identifier for the tool component in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "name": { + "description": "The name of the tool component.", + "type": "string" + }, + + "organization": { + "description": "The organization or company that produced the tool component.", + "type": "string" + }, + + "product": { + "description": "A product suite to which the tool component belongs.", + "type": "string" + }, + + "productSuite": { + "description": "A localizable string containing the name of the suite of products to which the tool component belongs.", + "type": "string" + }, + + "shortDescription": { + "description": "A brief description of the tool component.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullDescription": { + "description": "A comprehensive description of the tool component.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullName": { + "description": "The name of the tool component along with its version and any other useful identifying information, such as its locale.", + "type": "string" + }, + + "version": { + "description": "The tool component version, in whatever format the component natively provides.", + "type": "string" + }, + + "semanticVersion": { + "description": "The tool component version in the format specified by Semantic Versioning 2.0.", + "type": "string" + }, + + "dottedQuadFileVersion": { + "description": "The binary version of the tool component's primary executable file expressed as four non-negative integers separated by a period (for operating systems that express file versions in this way).", + "type": "string", + "pattern": "[0-9]+(\\.[0-9]+){3}" + }, + + "releaseDateUtc": { + "description": "A string specifying the UTC date (and optionally, the time) of the component's release.", + "type": "string" + }, + + "downloadUri": { + "description": "The absolute URI from which the tool component can be downloaded.", + "type": "string", + "format": "uri" + }, + + "informationUri": { + "description": "The absolute URI at which information about this version of the tool component can be found.", + "type": "string", + "format": "uri" + }, + + "globalMessageStrings": { + "description": "A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "notifications": { + "description": "An array of reportingDescriptor objects relevant to the notifications related to the configuration and runtime execution of the tool component.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/reportingDescriptor" + } + }, + + "rules": { + "description": "An array of reportingDescriptor objects relevant to the analysis performed by the tool component.", + "type": "array", + "minItems": 0, + "uniqueItems": false, + "default": [], + "items": { + "$ref": "#/definitions/reportingDescriptor" + } + }, + + "taxa": { + "description": "An array of reportingDescriptor objects relevant to the definitions of both standalone and tool-defined taxonomies.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/reportingDescriptor" + } + }, + + "locations": { + "description": "An array of the artifactLocation objects associated with the tool component.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "language": { + "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase language code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", + "type": "string", + "default": "en-US", + "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" + }, + + "contents": { + "description": "The kinds of data contained in this object.", + "type": "array", + "uniqueItems": true, + "default": [ "localizedData", "nonLocalizedData" ], + "items": { + "enum": [ + "localizedData", + "nonLocalizedData" + ], + "type": "string" + } + }, + + "isComprehensive": { + "description": "Specifies whether this object contains a complete definition of the localizable and/or non-localizable data for this component, as opposed to including only data that is relevant to the results persisted to this log file.", + "type": "boolean", + "default": false + }, + + "localizedDataSemanticVersion": { + "description": "The semantic version of the localized strings defined in this component; maintained by components that provide translations.", + "type": "string" + }, + + "minimumRequiredLocalizedDataSemanticVersion": { + "description": "The minimum value of localizedDataSemanticVersion required in translations consumed by this component; used by components that consume translations.", + "type": "string" + }, + + "associatedComponent": { + "description": "The component which is strongly associated with this component. For a translation, this refers to the component which has been translated. For an extension, this is the driver that provides the extension's plugin model.", + "$ref": "#/definitions/toolComponentReference" + }, + + "translationMetadata": { + "description": "Translation metadata, required for a translation, not populated by other component types.", + "$ref": "#/definitions/translationMetadata" + }, + + "supportedTaxonomies": { + "description": "An array of toolComponentReference objects to declare the taxonomies supported by the tool component.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponentReference" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the tool component.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "name" ] + }, + + "toolComponentReference": { + "description": "Identifies a particular toolComponent object, either the driver or an extension.", + "type": "object", + "additionalProperties": false, + "properties": { + + "name": { + "description": "The 'name' property of the referenced toolComponent.", + "type": "string" + }, + + "index": { + "description": "An index into the referenced toolComponent in tool.extensions.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "guid": { + "description": "The 'guid' property of the referenced toolComponent.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the toolComponentReference.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "translationMetadata": { + "description": "Provides additional metadata related to translation.", + "type": "object", + "additionalProperties": false, + "properties": { + + "name": { + "description": "The name associated with the translation metadata.", + "type": "string" + }, + + "fullName": { + "description": "The full name associated with the translation metadata.", + "type": "string" + }, + + "shortDescription": { + "description": "A brief description of the translation metadata.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullDescription": { + "description": "A comprehensive description of the translation metadata.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "downloadUri": { + "description": "The absolute URI from which the translation metadata can be downloaded.", + "type": "string", + "format": "uri" + }, + + "informationUri": { + "description": "The absolute URI from which information related to the translation metadata can be downloaded.", + "type": "string", + "format": "uri" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the translation metadata.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "name" ] + }, + + "versionControlDetails": { + "description": "Specifies the information necessary to retrieve a desired revision from a version control system.", + "type": "object", + "additionalProperties": false, + "properties": { + + "repositoryUri": { + "description": "The absolute URI of the repository.", + "type": "string", + "format": "uri" + }, + + "revisionId": { + "description": "A string that uniquely and permanently identifies the revision within the repository.", + "type": "string" + }, + + "branch": { + "description": "The name of a branch containing the revision.", + "type": "string" + }, + + "revisionTag": { + "description": "A tag that has been applied to the revision.", + "type": "string" + }, + + "asOfTimeUtc": { + "description": "A Coordinated Universal Time (UTC) date and time that can be used to synchronize an enlistment to the state of the repository at that time.", + "type": "string", + "format": "date-time" + }, + + "mappedTo": { + "description": "The location in the local file system to which the root of the repository was mapped at the time of the analysis.", + "$ref": "#/definitions/artifactLocation" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the version control details.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "repositoryUri" ] + }, + + "webRequest": { + "description": "Describes an HTTP request.", + "type": "object", + "additionalProperties": false, + "properties": { + + "index": { + "description": "The index within the run.webRequests array of the request object associated with this result.", + "type": "integer", + "default": -1, + "minimum": -1 + + }, + + "protocol": { + "description": "The request protocol. Example: 'http'.", + "type": "string" + }, + + "version": { + "description": "The request version. Example: '1.1'.", + "type": "string" + }, + + "target": { + "description": "The target of the request.", + "type": "string" + }, + + "method": { + "description": "The HTTP method. Well-known values are 'GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'.", + "type": "string" + }, + + "headers": { + "description": "The request headers.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "parameters": { + "description": "The request parameters.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "body": { + "description": "The body of the request.", + "$ref": "#/definitions/artifactContent" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the request.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "webResponse": { + "description": "Describes the response to an HTTP request.", + "type": "object", + "additionalProperties": false, + "properties": { + + "index": { + "description": "The index within the run.webResponses array of the response object associated with this result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "protocol": { + "description": "The response protocol. Example: 'http'.", + "type": "string" + }, + + "version": { + "description": "The response version. Example: '1.1'.", + "type": "string" + }, + + "statusCode": { + "description": "The response status code. Example: 451.", + "type": "integer" + }, + + "reasonPhrase": { + "description": "The response reason. Example: 'Not found'.", + "type": "string" + }, + + "headers": { + "description": "The response headers.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "body": { + "description": "The body of the response.", + "$ref": "#/definitions/artifactContent" + }, + + "noResponseReceived": { + "description": "Specifies whether a response was received from the server.", + "type": "boolean", + "default": false + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the response.", + "$ref": "#/definitions/propertyBag" + } + } + } + } +} From 79920e82994eb7545f576fcb210796ef842b9c5a Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Wed, 23 Oct 2024 17:47:50 +0200 Subject: [PATCH 02/23] Add SARIF parser --- .../service/BuildJobExecutionService.java | 2 +- .../strategy/sarif/IdCategorizer.java | 9 ++ .../strategy/sarif/RuleCategorizer.java | 6 + .../scaparser/strategy/sarif/SarifParser.java | 114 ++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java index 75bbaf826b00..5e16a58b142e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java @@ -418,7 +418,7 @@ private boolean isValidTestResultFile(TarArchiveEntry tarArchiveEntry) { String result = (lastIndexOfSlash != -1 && lastIndexOfSlash + 1 < name.length()) ? name.substring(lastIndexOfSlash + 1) : name; // Java test result files are named "TEST-*.xml", Python test result files are named "*results.xml". - return !tarArchiveEntry.isDirectory() && result.endsWith(".xml") && !result.equals("pom.xml"); + return !tarArchiveEntry.isDirectory() && (result.endsWith(".xml") && !result.equals("pom.xml") || result.endsWith(".sarif")); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java new file mode 100644 index 000000000000..c4f9e1a1bc5d --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java @@ -0,0 +1,9 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif; + +class IdCategorizer implements RuleCategorizer { + + @Override + public String categorizeRule(ReportingDescriptor rule) { + return rule.getId(); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java new file mode 100644 index 000000000000..6a709fcc9f8f --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java @@ -0,0 +1,6 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif; + +public interface RuleCategorizer { + + String categorizeRule(ReportingDescriptor rule); +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java new file mode 100644 index 000000000000..df06dea30b7c --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java @@ -0,0 +1,114 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisTool; +import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisIssue; +import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.ParserStrategy; + +/** + * Implements parts of the SARIF OASIS standard version 2.1.0. + * + * @see SARIF specification + */ +public class SarifParser implements ParserStrategy { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private final StaticCodeAnalysisTool tool; + + private final RuleCategorizer ruleCategorizer; + + public SarifParser(StaticCodeAnalysisTool tool, RuleCategorizer ruleCategorizer) { + this.tool = tool; + this.ruleCategorizer = ruleCategorizer; + } + + @Override + public StaticCodeAnalysisReportDTO parse(String reportContent) { + SarifLog sarifLog; + try { + sarifLog = objectMapper.readValue(reportContent, SarifLog.class); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + Run run = sarifLog.getRuns().getFirst(); + ToolComponent driver = run.getTool().getDriver(); + + List rules = driver.getRules().orElse(List.of()); + + // Rule ids are not guaranteed to be unique. Use the first occurring for rule lookup. + Map ruleOfId = rules.stream().collect(Collectors.toMap(ReportingDescriptor::getId, Function.identity(), (first, next) -> first)); + + List results = run.getResults().orElse(List.of()); + List issues = results.stream().map(result -> processResult(result, driver, ruleOfId)).toList(); + + return new StaticCodeAnalysisReportDTO(tool, issues); + } + + private StaticCodeAnalysisIssue processResult(Result result, ToolComponent driver, Map ruleOfId) { + PhysicalLocation location = result.getLocations().flatMap(locations -> locations.stream().findFirst()).flatMap(Location::getPhysicalLocation) + .orElseThrow(() -> new RuntimeException("Location needed")); + + URI uri = URI.create(location.getArtifactLocation().flatMap(ArtifactLocation::getUri).orElseThrow(() -> new RuntimeException("File path needed"))); + String path = uri.getPath(); + + Region region = location.getRegion().orElseThrow(() -> new RuntimeException("Region must be present")); + int startLine = region.getStartLine().orElseThrow(() -> new RuntimeException("Text region needed")); + int startColumn = region.getStartColumn().orElse(1); + int endLine = region.getEndLine().orElse(startLine); + int endColumn = region.getEndColumn().orElse(startColumn + 1); + + String ruleId = result.getRuleId() + .orElseGet(() -> result.getRule().flatMap(ReportingDescriptorReference::getId).orElseThrow(() -> new RuntimeException("Either ruleId or rule.id must be present"))); + + // ruleIndex can use -1 to indicate a missing value + Optional ruleIndexOrMinusOne = result.getRuleIndex().or(() -> result.getRule().flatMap(ReportingDescriptorReference::getIndex)); + Optional ruleIndex = ruleIndexOrMinusOne.flatMap(index -> index != -1 ? Optional.of(index) : Optional.empty()); + + Optional ruleByIndex = driver.getRules().flatMap(rules -> ruleIndex.map(rules::get)); + Optional rule = ruleByIndex.or(() -> lookupRuleById(ruleId, ruleOfId)); + + // Fallback to the rule identifier for the category + String category = rule.map(ruleCategorizer::categorizeRule).orElse(ruleId); + + Result.Level level = result.getLevel().orElse(Result.Level.WARNING); + + String message = result.getMessage().getText().orElseGet(() -> { + String messageId = result.getMessage().getId().orElseThrow(() -> new RuntimeException("Either text or id must be present")); + + var ruleMessageString = rule.flatMap(ReportingDescriptor::getMessageStrings).map(MessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); + var globalMessageString = driver.getGlobalMessageStrings().map(GlobalMessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); + + var messageString = ruleMessageString.or(() -> globalMessageString).orElseThrow(() -> new RuntimeException("Message lookup failed")); + return messageString.getText(); + }); + + return new StaticCodeAnalysisIssue(path, startLine, endLine, startColumn, endColumn, ruleId, category, message, level.toString(), null); + } + + private static Optional lookupRuleById(String ruleId, Map ruleOfId) { + return Optional.ofNullable(ruleOfId.get(ruleId)).or(() -> getBaseRuleId(ruleId).map(ruleOfId::get)); + } + + private static Optional getBaseRuleId(String ruleId) { + int hierarchySeperatorIndex = ruleId.lastIndexOf('/'); + if (hierarchySeperatorIndex == -1) { + return Optional.empty(); + } + String baseRuleId = ruleId.substring(0, hierarchySeperatorIndex); + return Optional.of(baseRuleId); + } + +} From 52ef50d839242d81e8f6d41d420613e1f7c6214d Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Wed, 23 Oct 2024 17:53:15 +0200 Subject: [PATCH 03/23] Remove command field from StaticCodeAnalysisTool --- .../domain/StaticCodeAnalysisTool.java | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) 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 9068d7fadd9c..a3ebd056ebbd 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 @@ -10,28 +10,23 @@ */ public enum StaticCodeAnalysisTool { - SPOTBUGS(ProgrammingLanguage.JAVA, "spotbugs:spotbugs", "spotbugsXml.xml"), CHECKSTYLE(ProgrammingLanguage.JAVA, "checkstyle:checkstyle", "checkstyle-result.xml"), - PMD(ProgrammingLanguage.JAVA, "pmd:pmd", "pmd.xml"), PMD_CPD(ProgrammingLanguage.JAVA, "pmd:cpd", "cpd.xml"), SWIFTLINT(ProgrammingLanguage.SWIFT, "", "swiftlint-result.xml"), - GCC(ProgrammingLanguage.C, "", "gcc.xml"); + // @formatter:off + SPOTBUGS(ProgrammingLanguage.JAVA, "spotbugsXml.xml"), + CHECKSTYLE(ProgrammingLanguage.JAVA, "checkstyle-result.xml"), + PMD(ProgrammingLanguage.JAVA, "pmd.xml"), + PMD_CPD(ProgrammingLanguage.JAVA, "cpd.xml"), + SWIFTLINT(ProgrammingLanguage.SWIFT, "swiftlint-result.xml"), + GCC(ProgrammingLanguage.C, "gcc.xml"), + ; + // @formatter:on private final ProgrammingLanguage language; - private final String command; + private final String fileName; - private final String filePattern; - - StaticCodeAnalysisTool(ProgrammingLanguage language, String command, String filePattern) { + StaticCodeAnalysisTool(ProgrammingLanguage language, String fileName) { this.language = language; - this.command = command; - this.filePattern = filePattern; - } - - public String getTask() { - return this.command; - } - - public String getFilePattern() { - return this.filePattern; + this.fileName = fileName; } /** @@ -58,7 +53,7 @@ public static List getToolsForProgrammingLanguage(Progra */ public static Optional getToolByFilePattern(String fileName) { for (StaticCodeAnalysisTool tool : StaticCodeAnalysisTool.values()) { - if (Objects.equals(fileName, tool.filePattern)) { + if (Objects.equals(fileName, tool.fileName)) { return Optional.of(tool); } } From 516cb41d818d2bcde802c2bebd0162a7b3bd40bf Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Fri, 25 Oct 2024 00:29:33 +0200 Subject: [PATCH 04/23] Remove language field from StaticCodeAnalysisTool --- .../domain/StaticCodeAnalysisTool.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) 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 a3ebd056ebbd..0569183512f0 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 @@ -1,7 +1,8 @@ package de.tum.cit.aet.artemis.programming.domain; -import java.util.ArrayList; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -11,21 +12,26 @@ public enum StaticCodeAnalysisTool { // @formatter:off - SPOTBUGS(ProgrammingLanguage.JAVA, "spotbugsXml.xml"), - CHECKSTYLE(ProgrammingLanguage.JAVA, "checkstyle-result.xml"), - PMD(ProgrammingLanguage.JAVA, "pmd.xml"), - PMD_CPD(ProgrammingLanguage.JAVA, "cpd.xml"), - SWIFTLINT(ProgrammingLanguage.SWIFT, "swiftlint-result.xml"), - GCC(ProgrammingLanguage.C, "gcc.xml"), + SPOTBUGS("spotbugsXml.xml"), + CHECKSTYLE("checkstyle-result.xml"), + PMD("pmd.xml"), + PMD_CPD("cpd.xml"), + SWIFTLINT("swiftlint-result.xml"), + GCC("gcc.xml"), ; // @formatter:on - private final ProgrammingLanguage language; + // @formatter:off + 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) + )); + // @formatter:on private final String fileName; - StaticCodeAnalysisTool(ProgrammingLanguage language, String fileName) { - this.language = language; + StaticCodeAnalysisTool(String fileName) { this.fileName = fileName; } @@ -36,13 +42,7 @@ public enum StaticCodeAnalysisTool { * @return List of static code analysis */ public static List getToolsForProgrammingLanguage(ProgrammingLanguage language) { - List tools = new ArrayList<>(); - for (var tool : StaticCodeAnalysisTool.values()) { - if (tool.language == language) { - tools.add(tool); - } - } - return tools; + return TOOLS_OF_PROGRAMMING_LANGUAGE.getOrDefault(language, List.of()); } /** From 6935b9ee13d8b1d3924458de31c604660bebabf0 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Fri, 25 Oct 2024 00:33:09 +0200 Subject: [PATCH 05/23] Add OTHER StaticCodeAnalysisTool --- .../aet/artemis/programming/domain/StaticCodeAnalysisTool.java | 1 + 1 file changed, 1 insertion(+) 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 0569183512f0..dd3ffcbea67a 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"), + OTHER(null), ; // @formatter:on From 61af0eb57647d688d73187934261979a20ac9826 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Tue, 22 Oct 2024 13:50:30 +0200 Subject: [PATCH 06/23] Rename XML variables --- .../buildagent/service/BuildJobExecutionService.java | 12 ++++++------ .../service/localci/scaparser/ReportParser.java | 4 ++-- .../localci/scaparser/strategy/CheckstyleParser.java | 4 ++-- .../localci/scaparser/strategy/PMDCPDParser.java | 4 ++-- .../localci/scaparser/strategy/PMDParser.java | 4 ++-- .../localci/scaparser/strategy/ParserStrategy.java | 6 +++--- .../localci/scaparser/strategy/SpotbugsParser.java | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java index 5e16a58b142e..39b3397b578b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java @@ -378,18 +378,18 @@ private BuildResult parseTestResults(TarArchiveInputStream testResultsTarInputSt } // Read the contents of the tar entry as a string. - String xmlString = readTarEntryContent(testResultsTarInputStream); + String fileString = readTarEntryContent(testResultsTarInputStream); // Get the file name of the tar entry. String fileName = getFileName(tarEntry); try { // Check if the file is a static code analysis report file if (StaticCodeAnalysisTool.getToolByFilePattern(fileName).isPresent()) { - processStaticCodeAnalysisReportFile(fileName, xmlString, staticCodeAnalysisReports, buildJobId); + processStaticCodeAnalysisReportFile(fileName, fileString, staticCodeAnalysisReports, buildJobId); } else { // ugly workaround because in swift result files \n\t breaks the parsing - var testResultFileString = xmlString.replace("\n\t", ""); + var testResultFileString = fileString.replace("\n\t", ""); if (!testResultFileString.isBlank()) { processTestResultFile(testResultFileString, failedTests, successfulTests); } @@ -444,12 +444,12 @@ private String getFileName(TarArchiveEntry tarEntry) { * Processes a static code analysis report file and adds the report to the corresponding list. * * @param fileName the file name of the static code analysis report file - * @param xmlString the content of the static code analysis report file + * @param reportContent the content of the static code analysis report file * @param staticCodeAnalysisReports the list of static code analysis reports */ - private void processStaticCodeAnalysisReportFile(String fileName, String xmlString, List staticCodeAnalysisReports, String buildJobId) { + private void processStaticCodeAnalysisReportFile(String fileName, String reportContent, List staticCodeAnalysisReports, String buildJobId) { try { - staticCodeAnalysisReports.add(ReportParser.getReport(xmlString, fileName)); + staticCodeAnalysisReports.add(ReportParser.getReport(reportContent, fileName)); } catch (UnsupportedToolException e) { String msg = "Failed to parse static code analysis report for " + fileName; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java index 9b8f59766ac2..063e3d934018 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java @@ -86,9 +86,9 @@ public static StaticCodeAnalysisReportDTO getReport(File file) throws IOExceptio return getReport(xmlContent, file.getName()); } - public static StaticCodeAnalysisReportDTO getReport(String xmlContent, String fileName) { + public static StaticCodeAnalysisReportDTO getReport(String reportContent, String fileName) { ParserPolicy parserPolicy = new ParserPolicy(); ParserStrategy parserStrategy = parserPolicy.configure(fileName); - return parserStrategy.parse(xmlContent); + return parserStrategy.parse(reportContent); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/CheckstyleParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/CheckstyleParser.java index e50be8d57997..f9fe1b9d90ba 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/CheckstyleParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/CheckstyleParser.java @@ -40,9 +40,9 @@ public class CheckstyleParser implements ParserStrategy { private final XmlMapper xmlMapper = new XmlMapper(); @Override - public StaticCodeAnalysisReportDTO parse(String xmlContent) { + public StaticCodeAnalysisReportDTO parse(String reportContent) { try { - List files = xmlMapper.readValue(xmlContent, new com.fasterxml.jackson.core.type.TypeReference>() { + List files = xmlMapper.readValue(reportContent, new com.fasterxml.jackson.core.type.TypeReference>() { }); return createReportFromFiles(files); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDCPDParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDCPDParser.java index 11fbab145fd4..b5da7a65fdf0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDCPDParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDCPDParser.java @@ -43,9 +43,9 @@ class PMDCPDParser implements ParserStrategy { private final XmlMapper xmlMapper = new XmlMapper(); @Override - public StaticCodeAnalysisReportDTO parse(String xmlContent) { + public StaticCodeAnalysisReportDTO parse(String reportContent) { try { - PmdCpc duplication = xmlMapper.readValue(xmlContent, PmdCpc.class); + PmdCpc duplication = xmlMapper.readValue(reportContent, PmdCpc.class); return createReportFromDuplication(duplication); } catch (IOException e) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDParser.java index ee7ce091b37b..7c7951630dc3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/PMDParser.java @@ -45,9 +45,9 @@ class PMDParser implements ParserStrategy { private final XmlMapper xmlMapper = new XmlMapper(); @Override - public StaticCodeAnalysisReportDTO parse(String xmlContent) { + public StaticCodeAnalysisReportDTO parse(String reportContent) { try { - PMDReport pmdReport = xmlMapper.readValue(xmlContent, PMDReport.class); + PMDReport pmdReport = xmlMapper.readValue(reportContent, PMDReport.class); return createReportFromPMDReport(pmdReport); } catch (IOException e) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserStrategy.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserStrategy.java index ea7e8e356179..cee6ca946062 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserStrategy.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/ParserStrategy.java @@ -12,10 +12,10 @@ static String transformToUnixPath(String path) { } /** - * Parse a static code analysis report from an XML string into a common Java representation. + * Parse a static code analysis report from a serialized string into a common Java representation. * - * @param xmlContent The XML content as a String + * @param reportContent The serialized content as a String * @return Report object containing the parsed report information */ - StaticCodeAnalysisReportDTO parse(String xmlContent); + StaticCodeAnalysisReportDTO parse(String reportContent); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/SpotbugsParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/SpotbugsParser.java index f4db7cbb351b..51e5e3f2cee5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/SpotbugsParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/SpotbugsParser.java @@ -49,9 +49,9 @@ class SpotbugsParser implements ParserStrategy { private final XmlMapper xmlMapper = new XmlMapper(); @Override - public StaticCodeAnalysisReportDTO parse(String xmlContent) { + public StaticCodeAnalysisReportDTO parse(String reportContent) { try { - BugCollection bugCollection = xmlMapper.readValue(xmlContent, BugCollection.class); + BugCollection bugCollection = xmlMapper.readValue(reportContent, BugCollection.class); return createReportFromBugCollection(bugCollection); } catch (IOException e) { From 89290ad9dafb3f7f75c892c8c8fe148f21dcbbbc Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Tue, 22 Oct 2024 14:00:57 +0200 Subject: [PATCH 07/23] Remove unused ReportParser parts --- .../localci/scaparser/ReportParser.java | 74 +--------------- .../scaparser/exception/ParserException.java | 16 ---- .../localci/scaparser/utils/ReportUtils.java | 48 ---------- .../StaticCodeAnalysisParserUnitTest.java | 88 ++++++------------- .../reports/cpd_invalid.txt | 43 --------- 5 files changed, 31 insertions(+), 238 deletions(-) delete mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/exception/ParserException.java delete mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/utils/ReportUtils.java delete mode 100644 src/test/resources/test-data/static-code-analysis/reports/cpd_invalid.txt diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java index 063e3d934018..bb9a0e73a4da 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java @@ -1,91 +1,23 @@ package de.tum.cit.aet.artemis.programming.service.localci.scaparser; -import static de.tum.cit.aet.artemis.programming.service.localci.scaparser.utils.ReportUtils.createErrorReport; -import static de.tum.cit.aet.artemis.programming.service.localci.scaparser.utils.ReportUtils.createFileTooLargeReport; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -import com.fasterxml.jackson.databind.ObjectMapper; - import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; -import de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception.ParserException; import de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception.UnsupportedToolException; import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.ParserPolicy; import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.ParserStrategy; -import de.tum.cit.aet.artemis.programming.service.localci.scaparser.utils.FileUtils; /** * Public API for parsing of static code analysis reports */ public class ReportParser { - private final ObjectMapper mapper = new ObjectMapper(); - - // Reports that are bigger then the threshold will not be parsed - // and an issue will be generated. The unit is in megabytes. - private static final int STATIC_CODE_ANALYSIS_REPORT_FILESIZE_LIMIT_IN_MB = 1; - - /** - * Transform a given static code analysis report into a JSON representation. - * All supported tools share the same JSON format. - * - * @param file Reference to the static code analysis report - * @return Static code analysis report represented as a JSON String - * @throws ParserException - If an exception occurs that is not already handled by the parser itself, e.g. caused by the json-parsing - */ - public String transformToJSONReport(File file) throws ParserException { - try { - StaticCodeAnalysisReportDTO report = transformToReport(file); - return mapper.writeValueAsString(report); - } - catch (Exception e) { - throw new ParserException(e.getMessage(), e); - } - } - /** - * Transform a given static code analysis report given as a file into a plain Java object. + * Builds the document using the provided string and parses it to a Report object. * - * @param file Reference to the static code analysis report - * @return Static code analysis report represented as a plain Java object - */ - public StaticCodeAnalysisReportDTO transformToReport(File file) { - if (file == null) { - throw new IllegalArgumentException("File must not be null"); - } - - // The static code analysis parser only supports xml files. - if (!FileUtils.getExtension(file).equals("xml")) { - throw new IllegalArgumentException("File must be xml format"); - } - try { - // Reject any file larger than the given threshold - if (FileUtils.isFilesizeGreaterThan(file, STATIC_CODE_ANALYSIS_REPORT_FILESIZE_LIMIT_IN_MB)) { - return createFileTooLargeReport(file.getName()); - } - - return getReport(file); - } - catch (Exception e) { - return createErrorReport(file.getName(), e); - } - } - - /** - * Builds the document using the provided file and parses it to a Report object using ObjectMapper. - * - * @param file File referencing the static code analysis report + * @param reportContent String containing the static code analysis report + * @param fileName filename of the report used for configuring a parser * @return Report containing the static code analysis issues * @throws UnsupportedToolException if the static code analysis tool which created the report is not supported - * @throws IOException if the file could not be read */ - public static StaticCodeAnalysisReportDTO getReport(File file) throws IOException { - String xmlContent = Files.readString(file.toPath()); - return getReport(xmlContent, file.getName()); - } - public static StaticCodeAnalysisReportDTO getReport(String reportContent, String fileName) { ParserPolicy parserPolicy = new ParserPolicy(); ParserStrategy parserStrategy = parserPolicy.configure(fileName); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/exception/ParserException.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/exception/ParserException.java deleted file mode 100644 index f0934ad041f3..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/exception/ParserException.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception; - -/** - * Exception thrown when an error occurs during parsing. - */ -public class ParserException extends Exception { - - /** - * Creates a new ParserException. - * - * @param message the detail message. - */ - public ParserException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/utils/ReportUtils.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/utils/ReportUtils.java deleted file mode 100644 index 3b38d2597a4f..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/utils/ReportUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.localci.scaparser.utils; - -import java.util.List; - -import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisTool; -import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisIssue; -import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; - -public final class ReportUtils { - - private ReportUtils() { - } - - /** - * Creates a report which states that the specified file is too large - * to be parsed by the parser. - * - * @param filename name of the parsed file - * @return report with the issue about the filesize - */ - public static StaticCodeAnalysisReportDTO createFileTooLargeReport(String filename) { - StaticCodeAnalysisTool tool = StaticCodeAnalysisTool.getToolByFilePattern(filename).orElse(null); - List issues = List.of(new StaticCodeAnalysisIssue(filename, 1, 1, 0, 0, // Assuming there are no column details - "TooManyIssues", "miscellaneous", String.format("There are too many issues found in the %s tool.", tool), null, // No priority for this issue - null // No penalty for this issue - )); - - return new StaticCodeAnalysisReportDTO(tool, issues); - } - - /** - * Creates a report wrapping an exception; Used to inform the client about any exception during parsing - * - * @param filename name of the parsed file - * @param exception exception to wrap - * @return a report for the file with an issue wrapping the exception - */ - public static StaticCodeAnalysisReportDTO createErrorReport(String filename, Exception exception) { - StaticCodeAnalysisTool tool = StaticCodeAnalysisTool.getToolByFilePattern(filename).orElse(null); - List issues = List.of(new StaticCodeAnalysisIssue(filename, 1, 1, 0, 0, // Assuming there are no column details - "ExceptionDuringParsing", "miscellaneous", - String.format("An exception occurred during parsing the report for %s. Exception: %s", tool != null ? tool : "file " + filename, exception), - // No priority and no penalty for this issue - null, null)); - - return new StaticCodeAnalysisReportDTO(tool, issues); - } -} 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 2e8886ec0fbc..946900357324 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 @@ -2,21 +2,20 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.fail; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; import de.tum.cit.aet.artemis.programming.service.localci.scaparser.ReportParser; -import de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception.ParserException; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception.UnsupportedToolException; /** * Tests each parser with an example file @@ -27,92 +26,61 @@ class StaticCodeAnalysisParserUnitTest { private static final Path REPORTS_FOLDER_PATH = Paths.get("src", "test", "resources", "test-data", "static-code-analysis", "reports"); + private final ObjectMapper mapper = new ObjectMapper(); + + private void testParserWithFile(String toolGeneratedReportFileName, String expectedJSONReportFileName) throws IOException { + testParserWithFileNamed(toolGeneratedReportFileName, toolGeneratedReportFileName, expectedJSONReportFileName); + } + /** * Compares the parsed JSON report with the expected JSON report * * @param toolGeneratedReportFileName The name of the file contains the report as generated by the different tools * @param expectedJSONReportFileName The name of the file that contains the parsed report - * @throws ParserException If an exception occurs that is not already handled by the parser itself, e.g. caused by the json-parsing */ - private void testParserWithFile(String toolGeneratedReportFileName, String expectedJSONReportFileName) throws ParserException, IOException { - File toolReport = REPORTS_FOLDER_PATH.resolve(toolGeneratedReportFileName).toFile(); + private void testParserWithFileNamed(String toolGeneratedReportFileName, String fileName, String expectedJSONReportFileName) throws IOException { + Path actualReportPath = REPORTS_FOLDER_PATH.resolve(toolGeneratedReportFileName); + File expectedJSONReportFile = EXPECTED_FOLDER_PATH.resolve(expectedJSONReportFileName).toFile(); - ReportParser parser = new ReportParser(); - String actual = parser.transformToJSONReport(toolReport); - - try (BufferedReader reader = Files.newBufferedReader(EXPECTED_FOLDER_PATH.resolve(expectedJSONReportFileName))) { - String expected = reader.lines().collect(Collectors.joining(System.lineSeparator())); - assertThat(actual).isEqualTo(expected); - } + String actualReportContent = Files.readString(actualReportPath); + testParserWithContent(actualReportContent, fileName, expectedJSONReportFile); } - private void testParserWithNullValue() throws ParserException { - ReportParser parser = new ReportParser(); - parser.transformToJSONReport(null); + private void testParserWithContent(String actualReportContent, String actualReportFilename, File expectedJSONReportFile) throws IOException { + StaticCodeAnalysisReportDTO actualReport = ReportParser.getReport(actualReportContent, actualReportFilename); + + StaticCodeAnalysisReportDTO expectedReport = mapper.readValue(expectedJSONReportFile, StaticCodeAnalysisReportDTO.class); + + assertThat(actualReport).isEqualTo(expectedReport); } @Test void testCheckstyleParser() throws IOException { - try { - testParserWithFile("checkstyle-result.xml", "checkstyle.txt"); - } - catch (ParserException e) { - fail("Checkstyle parser failed with exception: " + e.getMessage()); - } + testParserWithFile("checkstyle-result.xml", "checkstyle.txt"); } @Test void testPMDCPDParser() throws IOException { - try { - testParserWithFile("cpd.xml", "pmd_cpd.txt"); - } - catch (ParserException e) { - fail("PMD-CPD parser failed with exception: " + e.getMessage()); - } + testParserWithFile("cpd.xml", "pmd_cpd.txt"); } @Test void testPMDParser() throws IOException { - try { - testParserWithFile("pmd.xml", "pmd.txt"); - } - catch (ParserException e) { - fail("PMD parser failed with exception: " + e.getMessage()); - } + testParserWithFile("pmd.xml", "pmd.txt"); } @Test void testSpotbugsParser() throws IOException { - try { - testParserWithFile("spotbugsXml.xml", "spotbugs.txt"); - } - catch (ParserException e) { - fail("Spotbugs parser failed with exception: " + e.getMessage()); - } + testParserWithFile("spotbugsXml.xml", "spotbugs.txt"); } @Test - void testParseInvalidFilename() { - assertThatCode(() -> testParserWithFile("cpd_invalid.txt", "invalid_filename.txt")).isInstanceOf(ParserException.class); - } - - @Test - void testParseInvalidXML() throws Exception { - testParserWithFile("invalid_xml.xml", "invalid_xml.txt"); + void testParseInvalidXML() throws IOException { + assertThatCode(() -> testParserWithFileNamed("invalid_xml.xml", "pmd.xml", "invalid_xml.txt")).isInstanceOf(RuntimeException.class); } @Test void testInvalidName() throws IOException { - try { - testParserWithFile("invalid_name.xml", "invalid_name.txt"); - } - catch (ParserException e) { - fail("Parser failed with exception: " + e.getMessage()); - } - } - - @Test - void testThrowsParserException() { - assertThatExceptionOfType(ParserException.class).isThrownBy(this::testParserWithNullValue); + assertThatCode(() -> testParserWithFile("invalid_name.xml", "invalid_name.txt")).isInstanceOf(UnsupportedToolException.class); } } diff --git a/src/test/resources/test-data/static-code-analysis/reports/cpd_invalid.txt b/src/test/resources/test-data/static-code-analysis/reports/cpd_invalid.txt deleted file mode 100644 index 135463d9785e..000000000000 --- a/src/test/resources/test-data/static-code-analysis/reports/cpd_invalid.txt +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - createRandomDatesList() throws ParseException { - int listLength = randomIntegerWithin(RANDOM_FLOOR, RANDOM_CEILING); - List list = new ArrayList<>(); - - SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy"); - Date lowestDate = dateFormat.parse("08.11.2016"); - Date highestDate = dateFormat.parse("03.11.2020"); - - for (int i = 0; i < listLength; i++) { - Date randomDate = randomDateWithin(lowestDate, highestDate); - list.add(randomDate); - } - return list; - } - - private static List createRandomDatesList2() throws ParseException {]]> - - - - - - - From 8521f2b4637a8110075668a6120c075fd9bdd233 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Fri, 25 Oct 2024 08:11:36 +0200 Subject: [PATCH 08/23] Add parser test --- .../strategy/sarif/SarifParserTest.java | 305 ++++++++++++++++++ .../util/ProgrammingExerciseFactory.java | 1 + 2 files changed, 306 insertions(+) create mode 100644 src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java new file mode 100644 index 000000000000..c551e55ea28c --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java @@ -0,0 +1,305 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisTool; +import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisIssue; +import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; + +class SarifParserTest { + + static class FullDescriptionCategorizer implements RuleCategorizer { + + @Override + public String categorizeRule(ReportingDescriptor rule) { + return rule.getFullDescription().orElseThrow().getText(); + } + } + + @Test + void testEmpty() { + String report = """ + { + "runs": [ + { + "tool": { + "driver": { + "rules": [] + } + }, + "results": [] + } + ] + } + """; + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new IdCategorizer()); + StaticCodeAnalysisReportDTO parsedReport = parser.parse(report); + + assertThat(parsedReport.issues()).isEmpty(); + } + + @Test + void testMetadata() { + String report = """ + { + "runs": [ + { + "tool": { + "driver": { + "rules": [ + { + "fullDescription": { + "text": "CATEGORY" + }, + "id": "RULE_ID" + } + ] + } + }, + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 10, + "startColumn": 20, + "endLine": 30, + "endColumn": 40 + } + } + } + ], + "message": { + "text": "MESSAGE" + }, + "ruleId": "RULE_ID", + "ruleIndex": 0 + } + ] + } + ] + } + """; + StaticCodeAnalysisIssue expected = new StaticCodeAnalysisIssue("/path/to/file.txt", 10, 30, 20, 40, "RULE_ID", "CATEGORY", "MESSAGE", "error", null); + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new FullDescriptionCategorizer()); + StaticCodeAnalysisReportDTO parsedReport = parser.parse(report); + + assertThat(parsedReport.issues()).singleElement().isEqualTo(expected); + } + + @Test + void testMessageLookup() { + String report = """ + { + "runs": [ + { + "tool": { + "driver": { + "rules": [ + { + "fullDescription": { + "text": "FULL_DESCRIPTION" + }, + "id": "A001", + "messageStrings": { + "MESSAGE_ID_A": { + "text": "RULE_MESSAGE_CONTENT_A" + } + } + } + ], + "globalMessageStrings": { + "MESSAGE_ID_A": { + "text": "GLOBAL_MESSAGE_CONTENT_A" + }, + "MESSAGE_ID_B": { + "text": "GLOBAL_MESSAGE_CONTENT_B" + } + } + } + }, + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "id": "MESSAGE_ID_A" + }, + "ruleId": "A001", + "ruleIndex": 0 + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "id": "MESSAGE_ID_B" + }, + "ruleId": "B001" + } + ] + } + ] + } + """; + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new IdCategorizer()); + StaticCodeAnalysisReportDTO parsedReport = parser.parse(report); + + assertThat(parsedReport.issues()).anyMatch(issue -> issue.rule().equals("A001") && issue.message().equals("RULE_MESSAGE_CONTENT_A")) + .anyMatch(issue -> issue.rule().equals("B001") && issue.message().equals("GLOBAL_MESSAGE_CONTENT_B")); + } + + @Test + void testHierarchicalRuleIdLookup() { + String report = """ + { + "runs": [ + { + "tool": { + "driver": { + "rules": [ + { + "fullDescription": { + "text": "FULL_DESCRIPTION" + }, + "id": "A123" + } + ] + } + }, + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "MESSAGE" + }, + "ruleId": "A123/subrule" + } + ] + } + ] + } + """; + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new FullDescriptionCategorizer()); + StaticCodeAnalysisReportDTO parsedReport = parser.parse(report); + + assertThat(parsedReport.issues()).singleElement().matches(issue -> issue.category().equals("FULL_DESCRIPTION")); + } + + @Test + void testRuleIndexLookup() { + String report = """ + { + "runs": [ + { + "tool": { + "driver": { + "rules": [ + { + "fullDescription": { + "text": "FULL_DESCRIPTION_A" + }, + "id": "RULE_ID" + }, + { + "fullDescription": { + "text": "FULL_DESCRIPTION_B" + }, + "id": "RULE_ID" + } + ] + } + }, + "results": [ + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "MESSAGE" + }, + "ruleId": "RULE_ID", + "ruleIndex": 1 + } + ] + } + ] + } + """; + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new FullDescriptionCategorizer()); + StaticCodeAnalysisReportDTO parsedReport = parser.parse(report); + + assertThat(parsedReport.issues()).singleElement().matches(issue -> issue.category().equals("FULL_DESCRIPTION_B")); + } + + @Test + void testInvalidJSON() { + String report = """ + { + "runs": [ + { + ] + } + """; + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new IdCategorizer()); + assertThatThrownBy(() -> parser.parse(report)).hasCauseInstanceOf(JsonProcessingException.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 e4dca9d8be57..bf4c207df1e6 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 OTHER -> "Other"; }; return new StaticCodeAnalysisIssue(Constants.STUDENT_WORKING_DIRECTORY + "/www/packagename/Class1.java", // filePath From 8442b1a705761290a4b2f7480438151b06360336 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Mon, 28 Oct 2024 21:19:19 +0100 Subject: [PATCH 09/23] Statically initialize ParserPolicy --- .../programming/service/localci/scaparser/ReportParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java index bb9a0e73a4da..f31cbcd0c63e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/ReportParser.java @@ -10,6 +10,8 @@ */ public class ReportParser { + private static final ParserPolicy parserPolicy = new ParserPolicy(); + /** * Builds the document using the provided string and parses it to a Report object. * @@ -19,7 +21,6 @@ public class ReportParser { * @throws UnsupportedToolException if the static code analysis tool which created the report is not supported */ public static StaticCodeAnalysisReportDTO getReport(String reportContent, String fileName) { - ParserPolicy parserPolicy = new ParserPolicy(); ParserStrategy parserStrategy = parserPolicy.configure(fileName); return parserStrategy.parse(reportContent); } From 8882e506783d156b0b1745819c5f39bcdff53af2 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Mon, 28 Oct 2024 21:52:28 +0100 Subject: [PATCH 10/23] Factor out methods in SarifParser --- .../scaparser/strategy/sarif/SarifParser.java | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java index df06dea30b7c..fdfd595138cf 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java @@ -70,12 +70,9 @@ private StaticCodeAnalysisIssue processResult(Result result, ToolComponent drive int endLine = region.getEndLine().orElse(startLine); int endColumn = region.getEndColumn().orElse(startColumn + 1); - String ruleId = result.getRuleId() - .orElseGet(() -> result.getRule().flatMap(ReportingDescriptorReference::getId).orElseThrow(() -> new RuntimeException("Either ruleId or rule.id must be present"))); + String ruleId = getRuleId(result); - // ruleIndex can use -1 to indicate a missing value - Optional ruleIndexOrMinusOne = result.getRuleIndex().or(() -> result.getRule().flatMap(ReportingDescriptorReference::getIndex)); - Optional ruleIndex = ruleIndexOrMinusOne.flatMap(index -> index != -1 ? Optional.of(index) : Optional.empty()); + Optional ruleIndex = getRuleIndex(result); Optional ruleByIndex = driver.getRules().flatMap(rules -> ruleIndex.map(rules::get)); Optional rule = ruleByIndex.or(() -> lookupRuleById(ruleId, ruleOfId)); @@ -85,17 +82,36 @@ private StaticCodeAnalysisIssue processResult(Result result, ToolComponent drive Result.Level level = result.getLevel().orElse(Result.Level.WARNING); - String message = result.getMessage().getText().orElseGet(() -> { - String messageId = result.getMessage().getId().orElseThrow(() -> new RuntimeException("Either text or id must be present")); + String message = findMessage(result, driver, rule); - var ruleMessageString = rule.flatMap(ReportingDescriptor::getMessageStrings).map(MessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); - var globalMessageString = driver.getGlobalMessageStrings().map(GlobalMessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); + return new StaticCodeAnalysisIssue(path, startLine, endLine, startColumn, endColumn, ruleId, category, message, level.toString(), null); + } - var messageString = ruleMessageString.or(() -> globalMessageString).orElseThrow(() -> new RuntimeException("Message lookup failed")); - return messageString.getText(); - }); + private static String getRuleId(Result result) { + return result.getRuleId() + .orElseGet(() -> result.getRule().flatMap(ReportingDescriptorReference::getId).orElseThrow(() -> new RuntimeException("Either ruleId or rule.id must be present"))); + } - return new StaticCodeAnalysisIssue(path, startLine, endLine, startColumn, endColumn, ruleId, category, message, level.toString(), null); + private static Optional getRuleIndex(Result result) { + // ruleIndex can use -1 to indicate a missing value + Optional ruleIndexOrMinusOne = result.getRuleIndex().or(() -> result.getRule().flatMap(ReportingDescriptorReference::getIndex)); + return ruleIndexOrMinusOne.flatMap(index -> index != -1 ? Optional.of(index) : Optional.empty()); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private static String findMessage(Result result, ToolComponent driver, Optional rule) { + return result.getMessage().getText().orElseGet(() -> lookupMessageById(result, driver, rule)); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private static String lookupMessageById(Result result, ToolComponent driver, Optional rule) { + String messageId = result.getMessage().getId().orElseThrow(() -> new RuntimeException("Either text or id must be present")); + + var ruleMessageString = rule.flatMap(ReportingDescriptor::getMessageStrings).map(MessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); + var globalMessageString = driver.getGlobalMessageStrings().map(GlobalMessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); + + var messageString = ruleMessageString.or(() -> globalMessageString).orElseThrow(() -> new RuntimeException("Message lookup failed")); + return messageString.getText(); } private static Optional lookupRuleById(String ruleId, Map ruleOfId) { From eb7b790355fe0c0821a48f586fc8f6b8f79ddf56 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Mon, 28 Oct 2024 22:00:09 +0100 Subject: [PATCH 11/23] Document RuleCategorizer --- .../localci/scaparser/strategy/sarif/RuleCategorizer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java index 6a709fcc9f8f..b66c3c5751e7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java @@ -2,5 +2,11 @@ public interface RuleCategorizer { + /** + * Categorizes a SARIF rule using a tool specific strategy. + * + * @param rule The reporting descriptor containing the rule details + * @return The identifier of the resulting category + */ String categorizeRule(ReportingDescriptor rule); } From 56b4aedd35b7b011196eecfbbb18b0540d709891 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Wed, 30 Oct 2024 17:11:08 +0100 Subject: [PATCH 12/23] Change pacakge of generated classes --- build.gradle | 1 + .../scaparser/strategy/sarif/IdCategorizer.java | 2 ++ .../scaparser/strategy/sarif/RuleCategorizer.java | 2 ++ .../scaparser/strategy/sarif/SarifParser.java | 12 ++++++++++++ src/main/resources/json/sarif-schema-2.1.0.json | 2 +- .../scaparser/strategy/sarif/SarifParserTest.java | 1 + 6 files changed, 19 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cb28beb7c452..391f454d67f9 100644 --- a/build.gradle +++ b/build.gradle @@ -155,6 +155,7 @@ private excludedClassFilesForReport(classDirectories) { exclude: [ "**/de/tum/cit/aet/artemis/**/domain/**/*_*", "**/de/tum/cit/aet/artemis/core/config/migration/entries/**", + "**/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/**", "**/gradle-wrapper.jar/**" ] ) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java index c4f9e1a1bc5d..b586e9fd06d9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java @@ -1,5 +1,7 @@ package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptor; + class IdCategorizer implements RuleCategorizer { @Override diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java index b66c3c5751e7..46898ec5d9c4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/RuleCategorizer.java @@ -1,5 +1,7 @@ package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptor; + public interface RuleCategorizer { /** diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java index fdfd595138cf..9f83aea205bf 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java @@ -13,6 +13,18 @@ import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisTool; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisIssue; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ArtifactLocation; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.GlobalMessageStrings; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.Location; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.MessageStrings; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.PhysicalLocation; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.Region; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptor; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptorReference; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.Result; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.Run; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.SarifLog; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ToolComponent; import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.ParserStrategy; /** diff --git a/src/main/resources/json/sarif-schema-2.1.0.json b/src/main/resources/json/sarif-schema-2.1.0.json index 339e4e61862a..13654ecc3a50 100644 --- a/src/main/resources/json/sarif-schema-2.1.0.json +++ b/src/main/resources/json/sarif-schema-2.1.0.json @@ -5,7 +5,7 @@ "description": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.", "additionalProperties": false, "type": "object", - "javaType": "de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif.SarifLog", + "javaType": "de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.SarifLog", "properties": { "$schema": { diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java index c551e55ea28c..7ba1fbb8f6fc 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java @@ -10,6 +10,7 @@ import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisTool; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisIssue; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; +import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptor; class SarifParserTest { From 41a049bd70fa221e747875b696190176b1c67510 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Wed, 23 Oct 2024 17:57:29 +0200 Subject: [PATCH 13/23] Add Ruff Python SCA --- .../config/StaticCodeAnalysisConfigurer.java | 72 ++++++++++++++++++- .../domain/StaticCodeAnalysisTool.java | 4 +- ...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 ++++++ .../util/ProgrammingExerciseFactory.java | 1 + 9 files changed, 146 insertions(+), 5 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 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..703d6ffeb537 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,73 @@ */ 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" + ); + // @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 +150,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/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index d86199310720..f759f7711ab5 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 @@ -51,7 +51,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..167a0a32667d --- /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.getProperties().map(PropertyBag::getAdditionalProperties).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 e51e9f84e749..59f98784041e 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:latest" + default: "ls1tum/artemis-python-docker:pr-3" c: # possible overrides: gcc, fact default: "ls1tum/artemis-c-docker:latest" 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..bdda9592acd3 --- /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 --output-format=sarif --output-file=ruff.sarif --exit-zero --select=ALL "${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..c67c45e5f9d7 --- /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 --output-format=sarif --output-file=ruff.sarif --exit-zero --select=ALL "${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/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"; }; From 46fda956df60582f75c3f238963ca7aebfdc50b1 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Tue, 22 Oct 2024 16:23:41 +0200 Subject: [PATCH 14/23] Add test Add test file --- .../StaticCodeAnalysisParserUnitTest.java | 5 + .../static-code-analysis/expected/ruff.json | 467 +++++++ .../static-code-analysis/reports/ruff.sarif | 1190 +++++++++++++++++ 3 files changed, 1662 insertions(+) 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/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/StaticCodeAnalysisParserUnitTest.java index 946900357324..20821ce8624d 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() throws IOException { assertThatCode(() -> testParserWithFileNamed("invalid_xml.xml", "pmd.xml", "invalid_xml.txt")).isInstanceOf(RuntimeException.class); 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..62b110b1c4fd --- /dev/null +++ b/src/test/resources/test-data/static-code-analysis/expected/ruff.json @@ -0,0 +1,467 @@ +{ + "tool": "RUFF", + "issues": [ + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", + "startLine": 1, + "endLine": 1, + "startColumn": 1, + "endColumn": 1, + "rule": "D100", + "category": "pydocstyle", + "message": "Missing docstring in public module", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", + "startLine": 1, + "endLine": 1, + "startColumn": 7, + "endColumn": 14, + "rule": "D101", + "category": "pydocstyle", + "message": "Missing docstring in public class", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", + "startLine": 5, + "endLine": 5, + "startColumn": 9, + "endColumn": 13, + "rule": "ANN201", + "category": "flake8-annotations", + "message": "Missing return type annotation for public function `sort`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", + "startLine": 5, + "endLine": 5, + "startColumn": 9, + "endColumn": 13, + "rule": "D102", + "category": "pydocstyle", + "message": "Missing docstring in public method", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/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": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 1, + "endLine": 1, + "startColumn": 1, + "endColumn": 1, + "rule": "D100", + "category": "pydocstyle", + "message": "Missing docstring in public module", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 4, + "endLine": 4, + "startColumn": 7, + "endColumn": 13, + "rule": "D101", + "category": "pydocstyle", + "message": "Missing docstring in public class", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 7, + "endLine": 7, + "startColumn": 9, + "endColumn": 17, + "rule": "ANN204", + "category": "flake8-annotations", + "message": "Missing return type annotation for special method `__init__`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 7, + "endLine": 7, + "startColumn": 9, + "endColumn": 17, + "rule": "D107", + "category": "pydocstyle", + "message": "Missing docstring in `__init__`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 7, + "endLine": 7, + "startColumn": 24, + "endColumn": 31, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `context`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 10, + "endLine": 10, + "startColumn": 9, + "endColumn": 18, + "rule": "ANN201", + "category": "flake8-annotations", + "message": "Missing return type annotation for public function `configure`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 10, + "endLine": 10, + "startColumn": 9, + "endColumn": 18, + "rule": "D102", + "category": "pydocstyle", + "message": "Missing docstring in public method", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/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": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 12, + "endLine": 12, + "startColumn": 13, + "endColumn": 18, + "rule": "T201", + "category": "flake8-print", + "message": "`print` found", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 12, + "endLine": 12, + "startColumn": 19, + "endColumn": 63, + "rule": "Q000", + "category": "flake8-quotes", + "message": "Single quotes found but double quotes preferred", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/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": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 15, + "endLine": 15, + "startColumn": 13, + "endColumn": 18, + "rule": "T201", + "category": "flake8-print", + "message": "`print` found", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "startLine": 15, + "endLine": 15, + "startColumn": 19, + "endColumn": 73, + "rule": "Q000", + "category": "flake8-quotes", + "message": "Single quotes found but double quotes preferred", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/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": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", + "startLine": 1, + "endLine": 1, + "startColumn": 1, + "endColumn": 1, + "rule": "D100", + "category": "pydocstyle", + "message": "Missing docstring in public module", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", + "startLine": 4, + "endLine": 4, + "startColumn": 7, + "endColumn": 19, + "rule": "D101", + "category": "pydocstyle", + "message": "Missing docstring in public class", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", + "startLine": 7, + "endLine": 7, + "startColumn": 9, + "endColumn": 21, + "rule": "ANN201", + "category": "flake8-annotations", + "message": "Missing return type annotation for public function `perform_sort`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", + "startLine": 7, + "endLine": 7, + "startColumn": 9, + "endColumn": 21, + "rule": "D102", + "category": "pydocstyle", + "message": "Missing docstring in public method", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", + "startLine": 7, + "endLine": 7, + "startColumn": 28, + "endColumn": 33, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `array`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 1, + "endLine": 1, + "startColumn": 1, + "endColumn": 1, + "rule": "D100", + "category": "pydocstyle", + "message": "Missing docstring in public module", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 4, + "endLine": 4, + "startColumn": 7, + "endColumn": 17, + "rule": "D101", + "category": "pydocstyle", + "message": "Missing docstring in public class", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 6, + "endLine": 6, + "startColumn": 9, + "endColumn": 21, + "rule": "ANN201", + "category": "flake8-annotations", + "message": "Missing return type annotation for public function `perform_sort`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 6, + "endLine": 6, + "startColumn": 9, + "endColumn": 21, + "rule": "D102", + "category": "pydocstyle", + "message": "Missing docstring in public method", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 6, + "endLine": 6, + "startColumn": 28, + "endColumn": 31, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `arr`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 16, + "endLine": 16, + "startColumn": 7, + "endColumn": 16, + "rule": "D101", + "category": "pydocstyle", + "message": "Missing docstring in public class", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 18, + "endLine": 18, + "startColumn": 9, + "endColumn": 21, + "rule": "ANN201", + "category": "flake8-annotations", + "message": "Missing return type annotation for public function `perform_sort`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 18, + "endLine": 18, + "startColumn": 9, + "endColumn": 21, + "rule": "D102", + "category": "pydocstyle", + "message": "Missing docstring in public method", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 18, + "endLine": 18, + "startColumn": 28, + "endColumn": 31, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `arr`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 21, + "endLine": 21, + "startColumn": 9, + "endColumn": 21, + "rule": "ANN202", + "category": "flake8-annotations", + "message": "Missing return type annotation for private function `__merge_sort`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 21, + "endLine": 21, + "startColumn": 28, + "endColumn": 31, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `arr`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 21, + "endLine": 21, + "startColumn": 33, + "endColumn": 36, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `low`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 21, + "endLine": 21, + "startColumn": 38, + "endColumn": 42, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `high`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 30, + "endLine": 30, + "startColumn": 9, + "endColumn": 16, + "rule": "ANN202", + "category": "flake8-annotations", + "message": "Missing return type annotation for private function `__merge`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 30, + "endLine": 30, + "startColumn": 23, + "endColumn": 26, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `arr`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 30, + "endLine": 30, + "startColumn": 28, + "endColumn": 31, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `low`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 30, + "endLine": 30, + "startColumn": 33, + "endColumn": 36, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `mid`", + "priority": "error" + }, + { + "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", + "startLine": 30, + "endLine": 30, + "startColumn": 38, + "endColumn": 42, + "rule": "ANN001", + "category": "flake8-annotations", + "message": "Missing type annotation for function argument `high`", + "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..7fd9621dfc99 --- /dev/null +++ b/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif @@ -0,0 +1,1190 @@ +{ + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" + }, + "region": { + "endColumn": 1, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "Missing docstring in public module" + }, + "ruleId": "D100" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" + }, + "region": { + "endColumn": 14, + "endLine": 1, + "startColumn": 7, + "startLine": 1 + } + } + } + ], + "message": { + "text": "Missing docstring in public class" + }, + "ruleId": "D101" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" + }, + "region": { + "endColumn": 13, + "endLine": 5, + "startColumn": 9, + "startLine": 5 + } + } + } + ], + "message": { + "text": "Missing return type annotation for public function `sort`" + }, + "ruleId": "ANN201" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" + }, + "region": { + "endColumn": 13, + "endLine": 5, + "startColumn": 9, + "startLine": 5 + } + } + } + ], + "message": { + "text": "Missing docstring in public method" + }, + "ruleId": "D102" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 1, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "Missing docstring in public module" + }, + "ruleId": "D100" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 13, + "endLine": 4, + "startColumn": 7, + "startLine": 4 + } + } + } + ], + "message": { + "text": "Missing docstring in public class" + }, + "ruleId": "D101" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 17, + "endLine": 7, + "startColumn": 9, + "startLine": 7 + } + } + } + ], + "message": { + "text": "Missing return type annotation for special method `__init__`" + }, + "ruleId": "ANN204" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 17, + "endLine": 7, + "startColumn": 9, + "startLine": 7 + } + } + } + ], + "message": { + "text": "Missing docstring in `__init__`" + }, + "ruleId": "D107" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 31, + "endLine": 7, + "startColumn": 24, + "startLine": 7 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `context`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 18, + "endLine": 10, + "startColumn": 9, + "startLine": 10 + } + } + } + ], + "message": { + "text": "Missing return type annotation for public function `configure`" + }, + "ruleId": "ANN201" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 18, + "endLine": 10, + "startColumn": 9, + "startLine": 10 + } + } + } + ], + "message": { + "text": "Missing docstring in public method" + }, + "ruleId": "D102" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 18, + "endLine": 12, + "startColumn": 13, + "startLine": 12 + } + } + } + ], + "message": { + "text": "`print` found" + }, + "ruleId": "T201" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 63, + "endLine": 12, + "startColumn": 19, + "startLine": 12 + } + } + } + ], + "message": { + "text": "Single quotes found but double quotes preferred" + }, + "ruleId": "Q000" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 18, + "endLine": 15, + "startColumn": 13, + "startLine": 15 + } + } + } + ], + "message": { + "text": "`print` found" + }, + "ruleId": "T201" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" + }, + "region": { + "endColumn": 73, + "endLine": 15, + "startColumn": 19, + "startLine": 15 + } + } + } + ], + "message": { + "text": "Single quotes found but double quotes preferred" + }, + "ruleId": "Q000" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" + }, + "region": { + "endColumn": 1, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "Missing docstring in public module" + }, + "ruleId": "D100" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" + }, + "region": { + "endColumn": 19, + "endLine": 4, + "startColumn": 7, + "startLine": 4 + } + } + } + ], + "message": { + "text": "Missing docstring in public class" + }, + "ruleId": "D101" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" + }, + "region": { + "endColumn": 21, + "endLine": 7, + "startColumn": 9, + "startLine": 7 + } + } + } + ], + "message": { + "text": "Missing return type annotation for public function `perform_sort`" + }, + "ruleId": "ANN201" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" + }, + "region": { + "endColumn": 21, + "endLine": 7, + "startColumn": 9, + "startLine": 7 + } + } + } + ], + "message": { + "text": "Missing docstring in public method" + }, + "ruleId": "D102" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" + }, + "region": { + "endColumn": 33, + "endLine": 7, + "startColumn": 28, + "startLine": 7 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `array`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 1, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "Missing docstring in public module" + }, + "ruleId": "D100" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 17, + "endLine": 4, + "startColumn": 7, + "startLine": 4 + } + } + } + ], + "message": { + "text": "Missing docstring in public class" + }, + "ruleId": "D101" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 21, + "endLine": 6, + "startColumn": 9, + "startLine": 6 + } + } + } + ], + "message": { + "text": "Missing return type annotation for public function `perform_sort`" + }, + "ruleId": "ANN201" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 21, + "endLine": 6, + "startColumn": 9, + "startLine": 6 + } + } + } + ], + "message": { + "text": "Missing docstring in public method" + }, + "ruleId": "D102" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 31, + "endLine": 6, + "startColumn": 28, + "startLine": 6 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `arr`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 16, + "endLine": 16, + "startColumn": 7, + "startLine": 16 + } + } + } + ], + "message": { + "text": "Missing docstring in public class" + }, + "ruleId": "D101" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 21, + "endLine": 18, + "startColumn": 9, + "startLine": 18 + } + } + } + ], + "message": { + "text": "Missing return type annotation for public function `perform_sort`" + }, + "ruleId": "ANN201" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 21, + "endLine": 18, + "startColumn": 9, + "startLine": 18 + } + } + } + ], + "message": { + "text": "Missing docstring in public method" + }, + "ruleId": "D102" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 31, + "endLine": 18, + "startColumn": 28, + "startLine": 18 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `arr`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 21, + "endLine": 21, + "startColumn": 9, + "startLine": 21 + } + } + } + ], + "message": { + "text": "Missing return type annotation for private function `__merge_sort`" + }, + "ruleId": "ANN202" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 31, + "endLine": 21, + "startColumn": 28, + "startLine": 21 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `arr`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 36, + "endLine": 21, + "startColumn": 33, + "startLine": 21 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `low`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 42, + "endLine": 21, + "startColumn": 38, + "startLine": 21 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `high`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 16, + "endLine": 30, + "startColumn": 9, + "startLine": 30 + } + } + } + ], + "message": { + "text": "Missing return type annotation for private function `__merge`" + }, + "ruleId": "ANN202" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 26, + "endLine": 30, + "startColumn": 23, + "startLine": 30 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `arr`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 31, + "endLine": 30, + "startColumn": 28, + "startLine": 30 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `low`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 36, + "endLine": 30, + "startColumn": 33, + "startLine": 30 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `mid`" + }, + "ruleId": "ANN001" + }, + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + }, + "region": { + "endColumn": 42, + "endLine": 30, + "startColumn": 38, + "startLine": 30 + } + } + } + ], + "message": { + "text": "Missing type annotation for function argument `high`" + }, + "ruleId": "ANN001" + } + ], + "tool": { + "driver": { + "informationUri": "https://github.com/astral-sh/ruff", + "name": "ruff", + "rules": [ + { + "fullDescription": { + "text": "## What it does\nChecks that function arguments have type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the types of function arguments. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany provided arguments match expectation.\n\n## Example\n\n```python\ndef foo(x): ...\n```\n\nUse instead:\n\n```python\ndef foo(x: int): ...\n```\n" + }, + "help": { + "text": "Missing type annotation for function argument `{name}`" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/missing-type-function-argument", + "id": "ANN001", + "properties": { + "id": "ANN001", + "kind": "flake8-annotations", + "name": "missing-type-function-argument", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing type annotation for function argument `{name}`" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks that public functions and methods have return type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the return types of functions. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany returned values, and the types expected by callers, match expectation.\n\n## Example\n```python\ndef add(a, b):\n return a + b\n```\n\nUse instead:\n```python\ndef add(a: int, b: int) -> int:\n return a + b\n```\n" + }, + "help": { + "text": "Missing return type annotation for public function `{name}`" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function", + "id": "ANN201", + "properties": { + "id": "ANN201", + "kind": "flake8-annotations", + "name": "missing-return-type-undocumented-public-function", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing return type annotation for public function `{name}`" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks that private functions and methods have return type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the return types of functions. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany returned values, and the types expected by callers, match expectation.\n\n## Example\n```python\ndef _add(a, b):\n return a + b\n```\n\nUse instead:\n```python\ndef _add(a: int, b: int) -> int:\n return a + b\n```\n" + }, + "help": { + "text": "Missing return type annotation for private function `{name}`" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/missing-return-type-private-function", + "id": "ANN202", + "properties": { + "id": "ANN202", + "kind": "flake8-annotations", + "name": "missing-return-type-private-function", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing return type annotation for private function `{name}`" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks that \"special\" methods, like `__init__`, `__new__`, and `__call__`, have\nreturn type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the return types of functions. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany returned values, and the types expected by callers, match expectation.\n\nNote that type checkers often allow you to omit the return type annotation for\n`__init__` methods, as long as at least one argument has a type annotation. To\nopt in to this behavior, use the `mypy-init-return` setting in your `pyproject.toml`\nor `ruff.toml` file:\n\n```toml\n[tool.ruff.lint.flake8-annotations]\nmypy-init-return = true\n```\n\n## Example\n```python\nclass Foo:\n def __init__(self, x: int):\n self.x = x\n```\n\nUse instead:\n```python\nclass Foo:\n def __init__(self, x: int) -> None:\n self.x = x\n```\n" + }, + "help": { + "text": "Missing return type annotation for special method `{name}`" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/missing-return-type-special-method", + "id": "ANN204", + "properties": { + "id": "ANN204", + "kind": "flake8-annotations", + "name": "missing-return-type-special-method", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing return type annotation for special method `{name}`" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for undocumented public module definitions.\n\n## Why is this bad?\nPublic modules should be documented via docstrings to outline their purpose\nand contents.\n\nGenerally, module docstrings should describe the purpose of the module and\nlist the classes, exceptions, functions, and other objects that are exported\nby the module, alongside a one-line summary of each.\n\nIf the module is a script, the docstring should be usable as its \"usage\"\nmessage.\n\nIf the codebase adheres to a standard format for module docstrings, follow\nthat format for consistency.\n\n## Example\n\n```python\nclass FasterThanLightError(ZeroDivisionError): ...\n\n\ndef calculate_speed(distance: float, time: float) -> float: ...\n```\n\nUse instead:\n\n```python\n\"\"\"Utility functions and classes for calculating speed.\n\nThis module provides:\n- FasterThanLightError: exception when FTL speed is calculated;\n- calculate_speed: calculate speed given distance and time.\n\"\"\"\n\n\nclass FasterThanLightError(ZeroDivisionError): ...\n\n\ndef calculate_speed(distance: float, time: float) -> float: ...\n```\n\n## Notebook behavior\nThis rule is ignored for Jupyter Notebooks.\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)\n" + }, + "help": { + "text": "Missing docstring in public module" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-module", + "id": "D100", + "properties": { + "id": "D100", + "kind": "pydocstyle", + "name": "undocumented-public-module", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing docstring in public module" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for undocumented public class definitions.\n\n## Why is this bad?\nPublic classes should be documented via docstrings to outline their purpose\nand behavior.\n\nGenerally, a class docstring should describe the class's purpose and list\nits public attributes and methods.\n\nIf the codebase adheres to a standard format for class docstrings, follow\nthat format for consistency.\n\n## Example\n```python\nclass Player:\n def __init__(self, name: str, points: int = 0) -> None:\n self.name: str = name\n self.points: int = points\n\n def add_points(self, points: int) -> None:\n self.points += points\n```\n\nUse instead (in the NumPy docstring format):\n```python\nclass Player:\n \"\"\"A player in the game.\n\n Attributes\n ----------\n name : str\n The name of the player.\n points : int\n The number of points the player has.\n\n Methods\n -------\n add_points(points: int) -> None\n Add points to the player's score.\n \"\"\"\n\n def __init__(self, name: str, points: int = 0) -> None:\n self.name: str = name\n self.points: int = points\n\n def add_points(self, points: int) -> None:\n self.points += points\n```\n\nOr (in the Google docstring format):\n```python\nclass Player:\n \"\"\"A player in the game.\n\n Attributes:\n name: The name of the player.\n points: The number of points the player has.\n \"\"\"\n\n def __init__(self, name: str, points: int = 0) -> None:\n self.name: str = name\n self.points: int = points\n\n def add_points(self, points: int) -> None:\n self.points += points\n```\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)\n" + }, + "help": { + "text": "Missing docstring in public class" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-class", + "id": "D101", + "properties": { + "id": "D101", + "kind": "pydocstyle", + "name": "undocumented-public-class", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing docstring in public class" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for undocumented public method definitions.\n\n## Why is this bad?\nPublic methods should be documented via docstrings to outline their purpose\nand behavior.\n\nGenerally, a method docstring should describe the method's behavior,\narguments, side effects, exceptions, return values, and any other\ninformation that may be relevant to the user.\n\nIf the codebase adheres to a standard format for method docstrings, follow\nthat format for consistency.\n\n## Example\n```python\nclass Cat(Animal):\n def greet(self, happy: bool = True):\n if happy:\n print(\"Meow!\")\n else:\n raise ValueError(\"Tried to greet an unhappy cat.\")\n```\n\nUse instead (in the NumPy docstring format):\n```python\nclass Cat(Animal):\n def greet(self, happy: bool = True):\n \"\"\"Print a greeting from the cat.\n\n Parameters\n ----------\n happy : bool, optional\n Whether the cat is happy, is True by default.\n\n Raises\n ------\n ValueError\n If the cat is not happy.\n \"\"\"\n if happy:\n print(\"Meow!\")\n else:\n raise ValueError(\"Tried to greet an unhappy cat.\")\n```\n\nOr (in the Google docstring format):\n```python\nclass Cat(Animal):\n def greet(self, happy: bool = True):\n \"\"\"Print a greeting from the cat.\n\n Args:\n happy: Whether the cat is happy, is True by default.\n\n Raises:\n ValueError: If the cat is not happy.\n \"\"\"\n if happy:\n print(\"Meow!\")\n else:\n raise ValueError(\"Tried to greet an unhappy cat.\")\n```\n\n## Options\n- `lint.pydocstyle.ignore-decorators`\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)\n" + }, + "help": { + "text": "Missing docstring in public method" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-method", + "id": "D102", + "properties": { + "id": "D102", + "kind": "pydocstyle", + "name": "undocumented-public-method", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing docstring in public method" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for public `__init__` method definitions that are missing\ndocstrings.\n\n## Why is this bad?\nPublic `__init__` methods are used to initialize objects. `__init__`\nmethods should be documented via docstrings to describe the method's\nbehavior, arguments, side effects, exceptions, and any other information\nthat may be relevant to the user.\n\nIf the codebase adheres to a standard format for `__init__` method docstrings,\nfollow that format for consistency.\n\n## Example\n```python\nclass City:\n def __init__(self, name: str, population: int) -> None:\n self.name: str = name\n self.population: int = population\n```\n\nUse instead:\n```python\nclass City:\n def __init__(self, name: str, population: int) -> None:\n \"\"\"Initialize a city with a name and population.\"\"\"\n self.name: str = name\n self.population: int = population\n```\n\n## Options\n- `lint.pydocstyle.ignore-decorators`\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Style Python Docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)\n" + }, + "help": { + "text": "Missing docstring in `__init__`" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-init", + "id": "D107", + "properties": { + "id": "D107", + "kind": "pydocstyle", + "name": "undocumented-public-init", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Missing docstring in `__init__`" + } + }, + { + "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[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": "## What it does\nChecks for inline strings that use single quotes or double quotes,\ndepending on the value of the [`lint.flake8-quotes.inline-quotes`] option.\n\n## Why is this bad?\nConsistency is good. Use either single or double quotes for inline\nstrings, but be consistent.\n\n## Example\n```python\nfoo = 'bar'\n```\n\nAssuming `inline-quotes` is set to `double`, use instead:\n```python\nfoo = \"bar\"\n```\n\n## Options\n- `lint.flake8-quotes.inline-quotes`\n\n## Formatter compatibility\nWe recommend against using this rule alongside the [formatter]. The\nformatter enforces consistent quotes for inline strings, making the rule\nredundant.\n\n[formatter]: https://docs.astral.sh/ruff/formatter\n" + }, + "help": { + "text": "Single quotes found but double quotes preferred" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/bad-quotes-inline-string", + "id": "Q000", + "properties": { + "id": "Q000", + "kind": "flake8-quotes", + "name": "bad-quotes-inline-string", + "problem.severity": "error" + }, + "shortDescription": { + "text": "Single quotes found but double quotes preferred" + } + }, + { + "fullDescription": { + "text": "## What it does\nChecks for `print` statements.\n\n## Why is this bad?\n`print` statements are useful in some situations (e.g., debugging), but\nshould typically be omitted from production code. `print` statements can\nlead to the accidental inclusion of sensitive information in logs, and are\nnot configurable by clients, unlike `logging` statements.\n\n## Example\n```python\ndef add_numbers(a, b):\n print(f\"The sum of {a} and {b} is {a + b}\")\n return a + b\n```\n\nUse instead:\n```python\ndef add_numbers(a, b):\n return a + b\n```\n\n## Fix safety\nThis rule's fix is marked as unsafe, as it may remove `print` statements\nthat are used beyond debugging purposes.\n" + }, + "help": { + "text": "`print` found" + }, + "helpUri": "https://docs.astral.sh/ruff/rules/print", + "id": "T201", + "properties": { + "id": "T201", + "kind": "flake8-print", + "name": "print", + "problem.severity": "error" + }, + "shortDescription": { + "text": "`print` found" + } + } + ], + "version": "0.6.9" + } + } + } + ], + "version": "2.1.0" +} \ No newline at end of file From 1d248cf14ddd963ce8995d551f54f8b7248fe4c6 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Sun, 10 Nov 2024 02:32:43 +0100 Subject: [PATCH 15/23] Handle Result exceptions locally --- .../scaparser/strategy/sarif/SarifParser.java | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java index 9f83aea205bf..d3d721ba8db9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java @@ -3,10 +3,14 @@ import java.net.URI; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -34,6 +38,22 @@ */ public class SarifParser implements ParserStrategy { + private static final Logger log = LoggerFactory.getLogger(SarifParser.class); + + private static class SarifFormatException extends RuntimeException { + + private SarifFormatException(String message) { + super(message); + } + } + + private static class InformationMissingException extends RuntimeException { + + private InformationMissingException(String message) { + super(message); + } + } + private final ObjectMapper objectMapper = new ObjectMapper(); private final StaticCodeAnalysisTool tool; @@ -64,20 +84,34 @@ public StaticCodeAnalysisReportDTO parse(String reportContent) { Map ruleOfId = rules.stream().collect(Collectors.toMap(ReportingDescriptor::getId, Function.identity(), (first, next) -> first)); List results = run.getResults().orElse(List.of()); - List issues = results.stream().map(result -> processResult(result, driver, ruleOfId)).toList(); + List issues = results.stream().map(result -> tryProcessResult(result, driver, ruleOfId)).filter(Objects::nonNull).toList(); return new StaticCodeAnalysisReportDTO(tool, issues); } - private StaticCodeAnalysisIssue processResult(Result result, ToolComponent driver, Map ruleOfId) { + private StaticCodeAnalysisIssue tryProcessResult(Result result, ToolComponent driver, Map ruleOfId) { + try { + return processResult(result, driver, ruleOfId); + } + catch (SarifFormatException | NullPointerException e) { + log.error("The result is malformed", e); + return null; + } + catch (InformationMissingException e) { + log.warn("The result does not contain required information", e); + return null; + } + } + + private StaticCodeAnalysisIssue processResult(Result result, ToolComponent driver, Map ruleOfId) throws SarifFormatException { PhysicalLocation location = result.getLocations().flatMap(locations -> locations.stream().findFirst()).flatMap(Location::getPhysicalLocation) - .orElseThrow(() -> new RuntimeException("Location needed")); + .orElseThrow(() -> new InformationMissingException("Location needed")); - URI uri = URI.create(location.getArtifactLocation().flatMap(ArtifactLocation::getUri).orElseThrow(() -> new RuntimeException("File path needed"))); + URI uri = URI.create(location.getArtifactLocation().flatMap(ArtifactLocation::getUri).orElseThrow(() -> new InformationMissingException("File path needed"))); String path = uri.getPath(); - Region region = location.getRegion().orElseThrow(() -> new RuntimeException("Region must be present")); - int startLine = region.getStartLine().orElseThrow(() -> new RuntimeException("Text region needed")); + Region region = location.getRegion().orElseThrow(() -> new SarifFormatException("Region must be present")); + int startLine = region.getStartLine().orElseThrow(() -> new InformationMissingException("Text region needed")); int startColumn = region.getStartColumn().orElse(1); int endLine = region.getEndLine().orElse(startLine); int endColumn = region.getEndColumn().orElse(startColumn + 1); @@ -99,9 +133,9 @@ private StaticCodeAnalysisIssue processResult(Result result, ToolComponent drive return new StaticCodeAnalysisIssue(path, startLine, endLine, startColumn, endColumn, ruleId, category, message, level.toString(), null); } - private static String getRuleId(Result result) { - return result.getRuleId() - .orElseGet(() -> result.getRule().flatMap(ReportingDescriptorReference::getId).orElseThrow(() -> new RuntimeException("Either ruleId or rule.id must be present"))); + private static String getRuleId(Result result) throws SarifFormatException { + return result.getRuleId().orElseGet( + () -> result.getRule().flatMap(ReportingDescriptorReference::getId).orElseThrow(() -> new SarifFormatException("Either ruleId or rule.id must be present"))); } private static Optional getRuleIndex(Result result) { @@ -111,18 +145,18 @@ private static Optional getRuleIndex(Result result) { } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private static String findMessage(Result result, ToolComponent driver, Optional rule) { + private static String findMessage(Result result, ToolComponent driver, Optional rule) throws SarifFormatException { return result.getMessage().getText().orElseGet(() -> lookupMessageById(result, driver, rule)); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private static String lookupMessageById(Result result, ToolComponent driver, Optional rule) { - String messageId = result.getMessage().getId().orElseThrow(() -> new RuntimeException("Either text or id must be present")); + private static String lookupMessageById(Result result, ToolComponent driver, Optional rule) throws SarifFormatException { + String messageId = result.getMessage().getId().orElseThrow(() -> new SarifFormatException("Either text or id must be present")); var ruleMessageString = rule.flatMap(ReportingDescriptor::getMessageStrings).map(MessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); var globalMessageString = driver.getGlobalMessageStrings().map(GlobalMessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); - var messageString = ruleMessageString.or(() -> globalMessageString).orElseThrow(() -> new RuntimeException("Message lookup failed")); + var messageString = ruleMessageString.or(() -> globalMessageString).orElseThrow(() -> new SarifFormatException("Message lookup failed")); return messageString.getText(); } From 2ed59746f0c194fe3384a1a93bb5a2889b6c5805 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Sun, 10 Nov 2024 03:00:55 +0100 Subject: [PATCH 16/23] Add more tests --- .../strategy/sarif/SarifParserTest.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java index 7ba1fbb8f6fc..a8ddb700bcc2 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java @@ -303,4 +303,174 @@ void testInvalidJSON() { SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new IdCategorizer()); assertThatThrownBy(() -> parser.parse(report)).hasCauseInstanceOf(JsonProcessingException.class); } + + @Test + void testFilterMalformedSarif() { + String report = """ + { + "runs": [ + { + "tool": { + "driver": {} + }, + "results": [ + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "VALID" + }, + "ruleId": "A001" + }, + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + } + } + } + ], + "message": { + "text": "REGION MISSING" + }, + "ruleId": "A002" + }, + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "NO_RULE_ID" + } + }, + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "id": "INVALID_MESSAGE_ID" + }, + "ruleId": "A004" + } + ] + } + ] + } + """; + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new IdCategorizer()); + StaticCodeAnalysisReportDTO parsedReport = parser.parse(report); + + assertThat(parsedReport.issues()).singleElement().matches(issue -> issue.rule().equals("A001")); + } + + @Test + void testFilterInformationMissing() { + String report = """ + { + "runs": [ + { + "tool": { + "driver": {} + }, + "results": [ + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "VALID" + }, + "ruleId": "A001" + }, + { + "message": { + "text": "LOCATION MISSING" + }, + "ruleId": "A002" + }, + { + "locations": [ + { + "physicalLocation": { + "region": { + "startLine": 1 + } + } + } + ], + "message": { + "text": "PATH MISSING" + }, + "ruleId": "A003" + }, + { + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///path/to/file.txt" + }, + "region": { + "byteOffset": 0, + "byteLength": 10 + } + } + } + ], + "message": { + "text": "NOT A TEXT REGION" + }, + "ruleId": "A004" + } + ] + } + ] + } + """; + + SarifParser parser = new SarifParser(StaticCodeAnalysisTool.OTHER, new IdCategorizer()); + StaticCodeAnalysisReportDTO parsedReport = parser.parse(report); + + assertThat(parsedReport.issues()).singleElement().matches(issue -> issue.rule().equals("A001")); + } } From 5dfd8ff2961f409920eadf814a3bb75aa1967c9c Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Sun, 10 Nov 2024 03:19:28 +0100 Subject: [PATCH 17/23] Extract location processing method --- .../scaparser/strategy/sarif/SarifParser.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java index d3d721ba8db9..06252cb8b6d3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java @@ -54,6 +54,9 @@ private InformationMissingException(String message) { } } + private record FileLocation(String path, int startLine, int endLine, int startColumn, int endColumn) { + } + private final ObjectMapper objectMapper = new ObjectMapper(); private final StaticCodeAnalysisTool tool; @@ -104,18 +107,9 @@ private StaticCodeAnalysisIssue tryProcessResult(Result result, ToolComponent dr } private StaticCodeAnalysisIssue processResult(Result result, ToolComponent driver, Map ruleOfId) throws SarifFormatException { - PhysicalLocation location = result.getLocations().flatMap(locations -> locations.stream().findFirst()).flatMap(Location::getPhysicalLocation) + FileLocation fileLocation = result.getLocations().flatMap(locations -> locations.stream().findFirst()).flatMap(Location::getPhysicalLocation).map(this::extractLocation) .orElseThrow(() -> new InformationMissingException("Location needed")); - URI uri = URI.create(location.getArtifactLocation().flatMap(ArtifactLocation::getUri).orElseThrow(() -> new InformationMissingException("File path needed"))); - String path = uri.getPath(); - - Region region = location.getRegion().orElseThrow(() -> new SarifFormatException("Region must be present")); - int startLine = region.getStartLine().orElseThrow(() -> new InformationMissingException("Text region needed")); - int startColumn = region.getStartColumn().orElse(1); - int endLine = region.getEndLine().orElse(startLine); - int endColumn = region.getEndColumn().orElse(startColumn + 1); - String ruleId = getRuleId(result); Optional ruleIndex = getRuleIndex(result); @@ -130,7 +124,21 @@ private StaticCodeAnalysisIssue processResult(Result result, ToolComponent drive String message = findMessage(result, driver, rule); - return new StaticCodeAnalysisIssue(path, startLine, endLine, startColumn, endColumn, ruleId, category, message, level.toString(), null); + return new StaticCodeAnalysisIssue(fileLocation.path(), fileLocation.startLine(), fileLocation.endLine(), fileLocation.startColumn(), fileLocation.endColumn(), ruleId, + category, message, level.toString(), null); + } + + private FileLocation extractLocation(PhysicalLocation location) { + URI uri = URI.create(location.getArtifactLocation().flatMap(ArtifactLocation::getUri).orElseThrow(() -> new InformationMissingException("File path needed"))); + + Region region = location.getRegion().orElseThrow(() -> new SarifFormatException("Region must be present")); + + int startLine = region.getStartLine().orElseThrow(() -> new InformationMissingException("Text region needed")); + int startColumn = region.getStartColumn().orElse(1); + int endLine = region.getEndLine().orElse(startLine); + int endColumn = region.getEndColumn().orElse(startColumn + 1); + + return new FileLocation(uri.getPath(), startLine, endLine, startColumn, endColumn); } private static String getRuleId(Result result) throws SarifFormatException { From 8f20007a044d6ba49c5d22fb6afdf81a4787c72b Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Thu, 14 Nov 2024 13:08:14 +0100 Subject: [PATCH 18/23] Use explicit type definitions instead of schema --- build.gradle | 16 - .../format/sarif/ArtifactLocation.java | 22 + .../format/sarif/GlobalMessageStrings.java | 14 + .../scaparser/format/sarif/Location.java | 23 + .../scaparser/format/sarif/Message.java | 30 + .../format/sarif/MessageStrings.java | 13 + .../sarif/MultiformatMessageString.java | 13 + .../format/sarif/PhysicalLocation.java | 30 + .../scaparser/format/sarif/PropertyBag.java | 14 + .../scaparser/format/sarif/Region.java | 46 + .../format/sarif/ReportingDescriptor.java | 112 + .../sarif/ReportingDescriptorReference.java | 37 + .../scaparser/format/sarif/Result.java | 171 + .../localci/scaparser/format/sarif/Run.java | 27 + .../scaparser/format/sarif/SarifLog.java | 18 + .../localci/scaparser/format/sarif/Tool.java | 13 + .../scaparser/format/sarif/ToolComponent.java | 37 + .../strategy/sarif/IdCategorizer.java | 2 +- .../scaparser/strategy/sarif/SarifParser.java | 47 +- .../resources/json/sarif-schema-2.1.0.json | 3390 ----------------- .../strategy/sarif/SarifParserTest.java | 2 +- 21 files changed, 646 insertions(+), 3431 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ArtifactLocation.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/GlobalMessageStrings.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Location.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Message.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MessageStrings.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MultiformatMessageString.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PhysicalLocation.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PropertyBag.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Region.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptor.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptorReference.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Result.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Run.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/SarifLog.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Tool.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ToolComponent.java delete mode 100644 src/main/resources/json/sarif-schema-2.1.0.json diff --git a/build.gradle b/build.gradle index 76d833864b38..46eace3d037a 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,6 @@ plugins { id "com.gorylenko.gradle-git-properties" version "2.4.2" id "org.owasp.dependencycheck" version "11.1.0" id "com.adarshr.test-logger" version "4.0.0" - id "org.jsonschema2pojo" version "1.2.2" } group = "de.tum.cit.aet.artemis" @@ -155,7 +154,6 @@ private excludedClassFilesForReport(classDirectories) { exclude: [ "**/de/tum/cit/aet/artemis/**/domain/**/*_*", "**/de/tum/cit/aet/artemis/core/config/migration/entries/**", - "**/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/**", "**/gradle-wrapper.jar/**" ] ) @@ -196,12 +194,6 @@ jacocoTestCoverageVerification { } check.dependsOn jacocoTestCoverageVerification -jsonSchema2Pojo { - targetDirectory = file(layout.buildDirectory.dir("generated/sources/js2p")) - useOptionalForGetters = true - inclusionLevel = "NON_EMPTY" -} - configurations { providedRuntime } @@ -652,14 +644,6 @@ checkstyle { maxErrors = 0 } -tasks.withType(Checkstyle).configureEach { - // Exclude generated sources - exclude { - it.file.toPath().startsWith(jsonSchema2Pojo.targetDirectory.toPath()) - } -} - - def isNonStable = { String version -> def stableKeyword = ["RELEASE", "FINAL", "GA"].any { it -> version.toUpperCase().contains(it) } def regex = /^[0-9,.v-]+(-r)?$/ diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ArtifactLocation.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ArtifactLocation.java new file mode 100644 index 000000000000..ab497fc272d9 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ArtifactLocation.java @@ -0,0 +1,22 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Specifies the location of an artifact. + * + * @param uri A string containing a valid relative or absolute URI. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record ArtifactLocation(String uri) { + + /** + * A string containing a valid relative or absolute URI. + */ + public Optional getOptionalUri() { + return Optional.ofNullable(uri); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/GlobalMessageStrings.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/GlobalMessageStrings.java new file mode 100644 index 000000000000..04293695820b --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/GlobalMessageStrings.java @@ -0,0 +1,14 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnySetter; + +/** + * A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings in plain text and + * (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string + * arguments. + */ +public record GlobalMessageStrings(@JsonAnySetter Map additionalProperties) { + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Location.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Location.java new file mode 100644 index 000000000000..20dbd72ef99d --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Location.java @@ -0,0 +1,23 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * A location within a programming artifact. + * + * @param physicalLocation A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that + * artifact. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record Location(PhysicalLocation physicalLocation) { + + /** + * A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact. + */ + public Optional getOptionalPhysicalLocation() { + return Optional.ofNullable(physicalLocation); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Message.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Message.java new file mode 100644 index 000000000000..d353df2e203e --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Message.java @@ -0,0 +1,30 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Encapsulates a message intended to be read by the end user. + * + * @param text A plain text message string. + * @param id The identifier for this message. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record Message(String text, String id) { + + /** + * A plain text message string. + */ + public Optional getOptionalText() { + return Optional.ofNullable(text); + } + + /** + * The identifier for this message. + */ + public Optional getOptionalId() { + return Optional.ofNullable(id); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MessageStrings.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MessageStrings.java new file mode 100644 index 000000000000..a888c306bcee --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MessageStrings.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnySetter; + +/** + * A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The + * strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments. + */ +public record MessageStrings(@JsonAnySetter Map additionalProperties) { + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MultiformatMessageString.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MultiformatMessageString.java new file mode 100644 index 000000000000..a08afe804d02 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/MultiformatMessageString.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * A message string or message format string rendered in multiple formats. + * + * @param text A plain text message string or format string. + * (Required) + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record MultiformatMessageString(String text) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PhysicalLocation.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PhysicalLocation.java new file mode 100644 index 000000000000..b122875e94be --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PhysicalLocation.java @@ -0,0 +1,30 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact. + * + * @param artifactLocation Specifies the location of an artifact. + * @param region A region within an artifact where a result was detected. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record PhysicalLocation(ArtifactLocation artifactLocation, Region region) { + + /** + * Specifies the location of an artifact. + */ + public Optional getOptionalArtifactLocation() { + return Optional.ofNullable(artifactLocation); + } + + /** + * A region within an artifact where a result was detected. + */ + public Optional getOptionalRegion() { + return Optional.ofNullable(region); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PropertyBag.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PropertyBag.java new file mode 100644 index 000000000000..ba3b0bb208fb --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/PropertyBag.java @@ -0,0 +1,14 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Key/value pairs that provide additional information about the object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record PropertyBag(@JsonAnySetter Map additionalProperties) { + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Region.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Region.java new file mode 100644 index 000000000000..f9ac3391df87 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Region.java @@ -0,0 +1,46 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * A region within an artifact where a result was detected. + * + * @param startLine The line number of the first character in the region. + * @param startColumn The column number of the first character in the region. + * @param endLine The line number of the last character in the region. + * @param endColumn The column number of the character following the end of the region. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record Region(Integer startLine, Integer startColumn, Integer endLine, Integer endColumn) { + + /** + * The line number of the first character in the region. + */ + public Optional getOptionalStartLine() { + return Optional.ofNullable(startLine); + } + + /** + * The column number of the first character in the region. + */ + public Optional getOptionalStartColumn() { + return Optional.ofNullable(startColumn); + } + + /** + * The line number of the last character in the region. + */ + public Optional getOptionalEndLine() { + return Optional.ofNullable(endLine); + } + + /** + * The column number of the character following the end of the region. + */ + public Optional getOptionalEndColumn() { + return Optional.ofNullable(endColumn); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptor.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptor.java new file mode 100644 index 000000000000..75552ee32d69 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptor.java @@ -0,0 +1,112 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.net.URI; +import java.util.Optional; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Metadata that describes a specific report produced by the tool, as part of the analysis it provides or its runtime reporting. + * + * @param id A stable, opaque identifier for the report. + * (Required) + * @param deprecatedIds An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool. + * @param guid A unique identifier for the reporting descriptor in the form of a GUID. + * @param deprecatedGuids An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool. + * @param name A report identifier that is understandable to an end user. + * @param deprecatedNames An array of readable identifiers by which this report was known in some previous version of the analysis tool. + * @param shortDescription A message string or message format string rendered in multiple formats. + * @param fullDescription A message string or message format string rendered in multiple formats. + * @param messageStrings A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and + * (optionally) Markdown format. + * The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string + * arguments. + * @param helpUri A URI where the primary documentation for the report can be found. + * @param help A message string or message format string rendered in multiple formats. + * @param properties Key/value pairs that provide additional information about the object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record ReportingDescriptor(String id, Set deprecatedIds, String guid, Set deprecatedGuids, String name, Set deprecatedNames, + MultiformatMessageString shortDescription, MultiformatMessageString fullDescription, MessageStrings messageStrings, URI helpUri, MultiformatMessageString help, + PropertyBag properties) { + + /** + * An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool. + */ + public Optional> getOptionalDeprecatedIds() { + return Optional.ofNullable(deprecatedIds); + } + + /** + * A unique identifier for the reporting descriptor in the form of a GUID. + */ + public Optional getOptionalGuid() { + return Optional.ofNullable(guid); + } + + /** + * An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool. + */ + public Optional> getOptionalDeprecatedGuids() { + return Optional.ofNullable(deprecatedGuids); + } + + /** + * A report identifier that is understandable to an end user. + */ + public Optional getOptionalName() { + return Optional.ofNullable(name); + } + + /** + * An array of readable identifiers by which this report was known in some previous version of the analysis tool. + */ + public Optional> getOptionalDeprecatedNames() { + return Optional.ofNullable(deprecatedNames); + } + + /** + * A message string or message format string rendered in multiple formats. + */ + public Optional getOptionalShortDescription() { + return Optional.ofNullable(shortDescription); + } + + /** + * A message string or message format string rendered in multiple formats. + */ + public Optional getOptionalFullDescription() { + return Optional.ofNullable(fullDescription); + } + + /** + * A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. + * The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments. + */ + public Optional getOptionalMessageStrings() { + return Optional.ofNullable(messageStrings); + } + + /** + * A URI where the primary documentation for the report can be found. + */ + public Optional getOptionalHelpUri() { + return Optional.ofNullable(helpUri); + } + + /** + * A message string or message format string rendered in multiple formats. + */ + public Optional getOptionalHelp() { + return Optional.ofNullable(help); + } + + /** + * Key/value pairs that provide additional information about the object. + */ + public Optional getOptionalProperties() { + return Optional.ofNullable(properties); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptorReference.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptorReference.java new file mode 100644 index 000000000000..f46e722e6b1b --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ReportingDescriptorReference.java @@ -0,0 +1,37 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.Objects; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Information about how to locate a relevant reporting descriptor. + * + * @param id The id of the descriptor. + * @param index The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on + * context. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record ReportingDescriptorReference(String id, Integer index) { + + public ReportingDescriptorReference(String id, Integer index) { + this.id = id; + this.index = Objects.requireNonNullElse(index, -1); + } + + /** + * The id of the descriptor. + */ + public Optional getOptionalId() { + return Optional.ofNullable(id); + } + + /** + * The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context. + */ + public Optional getOptionalIndex() { + return Optional.ofNullable(index); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Result.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Result.java new file mode 100644 index 000000000000..49d17e49649a --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Result.java @@ -0,0 +1,171 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * A result produced by an analysis tool. + * + * @param ruleId The stable, unique identifier of the rule, if any, to which this result is relevant. + * @param ruleIndex The index within the tool component rules array of the rule object associated with this result. + * @param rule Information about how to locate a relevant reporting descriptor. + * @param kind A value that categorizes results by evaluation state. + * @param level A value specifying the severity level of the result. + * @param message A message that describes the result. The first sentence of the message only will be displayed when visible space is limited. + * @param locations The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a + * change at every specified location. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record Result(String ruleId, Integer ruleIndex, ReportingDescriptorReference rule, + de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.Result.Kind kind, + de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.Result.Level level, Message message, List locations) { + + public Result(String ruleId, Integer ruleIndex, ReportingDescriptorReference rule, Kind kind, Level level, Message message, List locations) { + this.ruleId = ruleId; + this.ruleIndex = Objects.requireNonNullElse(ruleIndex, -1); + this.rule = rule; + this.kind = Objects.requireNonNullElse(kind, Kind.FAIL); + this.level = Objects.requireNonNullElse(level, Level.WARNING); + this.message = message; + this.locations = locations; + } + + /** + * The stable, unique identifier of the rule, if any, to which this result is relevant. + */ + public Optional getOptionalRuleId() { + return Optional.ofNullable(ruleId); + } + + /** + * The index within the tool component rules array of the rule object associated with this result. + */ + public Optional getOptionalRuleIndex() { + return Optional.ofNullable(ruleIndex); + } + + /** + * Information about how to locate a relevant reporting descriptor. + */ + public Optional getOptionalRule() { + return Optional.ofNullable(rule); + } + + /** + * A value that categorizes results by evaluation state. + */ + public Optional getOptionalKind() { + return Optional.ofNullable(kind); + } + + /** + * A value specifying the severity level of the result. + */ + public Optional getOptionalLevel() { + return Optional.ofNullable(level); + } + + /** + * The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every + * specified location. + */ + public Optional> getOptionalLocations() { + return Optional.ofNullable(locations); + } + + /** + * A value that categorizes results by evaluation state. + */ + public enum Kind { + + NOT_APPLICABLE("notApplicable"), PASS("pass"), FAIL("fail"), REVIEW("review"), OPEN("open"), INFORMATIONAL("informational"); + + private final String value; + + private final static Map CONSTANTS = new HashMap<>(); + + static { + for (Kind c : values()) { + CONSTANTS.put(c.value, c); + } + } + + Kind(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Kind fromValue(String value) { + Kind constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } + else { + return constant; + } + } + + } + + /** + * A value specifying the severity level of the result. + */ + public enum Level { + + NONE("none"), NOTE("note"), WARNING("warning"), ERROR("error"); + + private final String value; + + private final static Map CONSTANTS = new HashMap<>(); + + static { + for (Level c : values()) { + CONSTANTS.put(c.value, c); + } + } + + Level(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Level fromValue(String value) { + Level constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } + else { + return constant; + } + } + + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Run.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Run.java new file mode 100644 index 000000000000..c77d9ccaee4b --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Run.java @@ -0,0 +1,27 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.List; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Describes a single run of an analysis tool, and contains the reported output of that run. + * + * @param tool The analysis tool that was run. + * (Required) + * @param results The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be + * empty) if a log file represents an actual scan. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record Run(Tool tool, List results) { + + /** + * The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be empty) if a log + * file represents an actual scan. + */ + public Optional> getOptionalResults() { + return Optional.ofNullable(results); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/SarifLog.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/SarifLog.java new file mode 100644 index 000000000000..28efb3370c53 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/SarifLog.java @@ -0,0 +1,18 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema + *

+ * Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools. + * + * @param runs The set of runs contained in this log file. + * (Required) + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record SarifLog(List runs) { + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Tool.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Tool.java new file mode 100644 index 000000000000..d6af4c944ba3 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/Tool.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * The analysis tool that was run. + * + * @param driver A component, such as a plug-in or the driver, of the analysis tool that was run. + * (Required) + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record Tool(ToolComponent driver) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ToolComponent.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ToolComponent.java new file mode 100644 index 000000000000..b7fb4cf33d3e --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/ToolComponent.java @@ -0,0 +1,37 @@ +package de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif; + +import java.util.List; +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * A component, such as a plug-in or the driver, of the analysis tool that was run. + * + * @param name The name of the tool component. + * (Required) + * @param globalMessageStrings A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings + * in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination + * with an arbitrary number of additional string arguments. + * @param rules An array of reportingDescriptor objects relevant to the analysis performed by the tool component. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record ToolComponent(String name, GlobalMessageStrings globalMessageStrings, List rules) { + + /** + * A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings in plain text and + * (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string + * arguments. + */ + public Optional getOptionalGlobalMessageStrings() { + return Optional.ofNullable(globalMessageStrings); + } + + /** + * An array of reportingDescriptor objects relevant to the analysis performed by the tool component. + */ + public Optional> getOptionalRules() { + return Optional.ofNullable(rules); + } + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java index b586e9fd06d9..646780fee2f5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/IdCategorizer.java @@ -6,6 +6,6 @@ class IdCategorizer implements RuleCategorizer { @Override public String categorizeRule(ReportingDescriptor rule) { - return rule.getId(); + return rule.id(); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java index 06252cb8b6d3..46905fa6ed01 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParser.java @@ -78,15 +78,15 @@ public StaticCodeAnalysisReportDTO parse(String reportContent) { throw new RuntimeException(e); } - Run run = sarifLog.getRuns().getFirst(); - ToolComponent driver = run.getTool().getDriver(); + Run run = sarifLog.runs().getFirst(); + ToolComponent driver = run.tool().driver(); - List rules = driver.getRules().orElse(List.of()); + List rules = driver.getOptionalRules().orElse(List.of()); // Rule ids are not guaranteed to be unique. Use the first occurring for rule lookup. - Map ruleOfId = rules.stream().collect(Collectors.toMap(ReportingDescriptor::getId, Function.identity(), (first, next) -> first)); + Map ruleOfId = rules.stream().collect(Collectors.toMap(ReportingDescriptor::id, Function.identity(), (first, next) -> first)); - List results = run.getResults().orElse(List.of()); + List results = run.getOptionalResults().orElse(List.of()); List issues = results.stream().map(result -> tryProcessResult(result, driver, ruleOfId)).filter(Objects::nonNull).toList(); return new StaticCodeAnalysisReportDTO(tool, issues); @@ -107,20 +107,20 @@ private StaticCodeAnalysisIssue tryProcessResult(Result result, ToolComponent dr } private StaticCodeAnalysisIssue processResult(Result result, ToolComponent driver, Map ruleOfId) throws SarifFormatException { - FileLocation fileLocation = result.getLocations().flatMap(locations -> locations.stream().findFirst()).flatMap(Location::getPhysicalLocation).map(this::extractLocation) - .orElseThrow(() -> new InformationMissingException("Location needed")); + FileLocation fileLocation = result.getOptionalLocations().flatMap(locations -> locations.stream().findFirst()).flatMap(Location::getOptionalPhysicalLocation) + .map(this::extractLocation).orElseThrow(() -> new InformationMissingException("Location needed")); String ruleId = getRuleId(result); Optional ruleIndex = getRuleIndex(result); - Optional ruleByIndex = driver.getRules().flatMap(rules -> ruleIndex.map(rules::get)); + Optional ruleByIndex = driver.getOptionalRules().flatMap(rules -> ruleIndex.map(rules::get)); Optional rule = ruleByIndex.or(() -> lookupRuleById(ruleId, ruleOfId)); // Fallback to the rule identifier for the category String category = rule.map(ruleCategorizer::categorizeRule).orElse(ruleId); - Result.Level level = result.getLevel().orElse(Result.Level.WARNING); + Result.Level level = result.getOptionalLevel().orElse(Result.Level.WARNING); String message = findMessage(result, driver, rule); @@ -129,43 +129,44 @@ private StaticCodeAnalysisIssue processResult(Result result, ToolComponent drive } private FileLocation extractLocation(PhysicalLocation location) { - URI uri = URI.create(location.getArtifactLocation().flatMap(ArtifactLocation::getUri).orElseThrow(() -> new InformationMissingException("File path needed"))); + URI uri = URI + .create(location.getOptionalArtifactLocation().flatMap(ArtifactLocation::getOptionalUri).orElseThrow(() -> new InformationMissingException("File path needed"))); - Region region = location.getRegion().orElseThrow(() -> new SarifFormatException("Region must be present")); + Region region = location.getOptionalRegion().orElseThrow(() -> new SarifFormatException("Region must be present")); - int startLine = region.getStartLine().orElseThrow(() -> new InformationMissingException("Text region needed")); - int startColumn = region.getStartColumn().orElse(1); - int endLine = region.getEndLine().orElse(startLine); - int endColumn = region.getEndColumn().orElse(startColumn + 1); + int startLine = region.getOptionalStartLine().orElseThrow(() -> new InformationMissingException("Text region needed")); + int startColumn = region.getOptionalStartColumn().orElse(1); + int endLine = region.getOptionalEndLine().orElse(startLine); + int endColumn = region.getOptionalEndColumn().orElse(startColumn + 1); return new FileLocation(uri.getPath(), startLine, endLine, startColumn, endColumn); } private static String getRuleId(Result result) throws SarifFormatException { - return result.getRuleId().orElseGet( - () -> result.getRule().flatMap(ReportingDescriptorReference::getId).orElseThrow(() -> new SarifFormatException("Either ruleId or rule.id must be present"))); + return result.getOptionalRuleId().orElseGet(() -> result.getOptionalRule().flatMap(ReportingDescriptorReference::getOptionalId) + .orElseThrow(() -> new SarifFormatException("Either ruleId or rule.id must be present"))); } private static Optional getRuleIndex(Result result) { // ruleIndex can use -1 to indicate a missing value - Optional ruleIndexOrMinusOne = result.getRuleIndex().or(() -> result.getRule().flatMap(ReportingDescriptorReference::getIndex)); + Optional ruleIndexOrMinusOne = result.getOptionalRuleIndex().or(() -> result.getOptionalRule().flatMap(ReportingDescriptorReference::getOptionalIndex)); return ruleIndexOrMinusOne.flatMap(index -> index != -1 ? Optional.of(index) : Optional.empty()); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static String findMessage(Result result, ToolComponent driver, Optional rule) throws SarifFormatException { - return result.getMessage().getText().orElseGet(() -> lookupMessageById(result, driver, rule)); + return result.message().getOptionalText().orElseGet(() -> lookupMessageById(result, driver, rule)); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static String lookupMessageById(Result result, ToolComponent driver, Optional rule) throws SarifFormatException { - String messageId = result.getMessage().getId().orElseThrow(() -> new SarifFormatException("Either text or id must be present")); + String messageId = result.message().getOptionalId().orElseThrow(() -> new SarifFormatException("Either text or id must be present")); - var ruleMessageString = rule.flatMap(ReportingDescriptor::getMessageStrings).map(MessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); - var globalMessageString = driver.getGlobalMessageStrings().map(GlobalMessageStrings::getAdditionalProperties).map(strings -> strings.get(messageId)); + var ruleMessageString = rule.flatMap(ReportingDescriptor::getOptionalMessageStrings).map(MessageStrings::additionalProperties).map(strings -> strings.get(messageId)); + var globalMessageString = driver.getOptionalGlobalMessageStrings().map(GlobalMessageStrings::additionalProperties).map(strings -> strings.get(messageId)); var messageString = ruleMessageString.or(() -> globalMessageString).orElseThrow(() -> new SarifFormatException("Message lookup failed")); - return messageString.getText(); + return messageString.text(); } private static Optional lookupRuleById(String ruleId, Map ruleOfId) { diff --git a/src/main/resources/json/sarif-schema-2.1.0.json b/src/main/resources/json/sarif-schema-2.1.0.json deleted file mode 100644 index 13654ecc3a50..000000000000 --- a/src/main/resources/json/sarif-schema-2.1.0.json +++ /dev/null @@ -1,3390 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema", - "id": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", - "description": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.", - "additionalProperties": false, - "type": "object", - "javaType": "de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.SarifLog", - "properties": { - - "$schema": { - "description": "The URI of the JSON schema corresponding to the version.", - "type": "string", - "format": "uri" - }, - - "version": { - "description": "The SARIF format version of this log file.", - "enum": [ "2.1.0" ], - "type": "string" - }, - - "runs": { - "description": "The set of runs contained in this log file.", - "type": [ "array", "null" ], - "minItems": 0, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/run" - } - }, - - "inlineExternalProperties": { - "description": "References to external property files that share data between runs.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/externalProperties" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the log file.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "version", "runs" ], - - "definitions": { - - "address": { - "description": "A physical or virtual address, or a range of addresses, in an 'addressable region' (memory or a binary file).", - "additionalProperties": false, - "type": "object", - "properties": { - - "absoluteAddress": { - "description": "The address expressed as a byte offset from the start of the addressable region.", - "type": "integer", - "minimum": -1, - "default": -1 - - }, - - "relativeAddress": { - "description": "The address expressed as a byte offset from the absolute address of the top-most parent object.", - "type": "integer" - - }, - - "length": { - "description": "The number of bytes in this range of addresses.", - "type": "integer" - }, - - "kind": { - "description": "An open-ended string that identifies the address kind. 'data', 'function', 'header','instruction', 'module', 'page', 'section', 'segment', 'stack', 'stackFrame', 'table' are well-known values.", - "type": "string" - }, - - "name": { - "description": "A name that is associated with the address, e.g., '.text'.", - "type": "string" - }, - - "fullyQualifiedName": { - "description": "A human-readable fully qualified name that is associated with the address.", - "type": "string" - }, - - "offsetFromParent": { - "description": "The byte offset of this address from the absolute or relative address of the parent object.", - "type": "integer" - }, - - "index": { - "description": "The index within run.addresses of the cached object for this address.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "parentIndex": { - "description": "The index within run.addresses of the parent object.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the address.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "artifact": { - "description": "A single artifact. In some cases, this artifact might be nested within another artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "description": { - "description": "A short description of the artifact.", - "$ref": "#/definitions/message" - }, - - "location": { - "description": "The location of the artifact.", - "$ref": "#/definitions/artifactLocation" - }, - - "parentIndex": { - "description": "Identifies the index of the immediate parent of the artifact, if this artifact is nested.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "offset": { - "description": "The offset in bytes of the artifact within its containing artifact.", - "type": "integer", - "minimum": 0 - }, - - "length": { - "description": "The length of the artifact in bytes.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "roles": { - "description": "The role or roles played by the artifact in the analysis.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "enum": [ - "analysisTarget", - "attachment", - "responseFile", - "resultFile", - "standardStream", - "tracedFile", - "unmodified", - "modified", - "added", - "deleted", - "renamed", - "uncontrolled", - "driver", - "extension", - "translation", - "taxonomy", - "policy", - "referencedOnCommandLine", - "memoryContents", - "directory", - "userSpecifiedConfiguration", - "toolSpecifiedConfiguration", - "debugOutputFile" - ], - "type": "string" - } - }, - - "mimeType": { - "description": "The MIME type (RFC 2045) of the artifact.", - "type": "string", - "pattern": "[^/]+/.+" - }, - - "contents": { - "description": "The contents of the artifact.", - "$ref": "#/definitions/artifactContent" - }, - - "encoding": { - "description": "Specifies the encoding for an artifact object that refers to a text file.", - "type": "string" - }, - - "sourceLanguage": { - "description": "Specifies the source language for any artifact object that refers to a text file that contains source code.", - "type": "string" - }, - - "hashes": { - "description": "A dictionary, each of whose keys is the name of a hash function and each of whose values is the hashed value of the artifact produced by the specified hash function.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "lastModifiedTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the artifact was most recently modified. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the artifact.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "artifactChange": { - "description": "A change to a single artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "artifactLocation": { - "description": "The location of the artifact to change.", - "$ref": "#/definitions/artifactLocation" - }, - - "replacements": { - "description": "An array of replacement objects, each of which represents the replacement of a single region in a single artifact specified by 'artifactLocation'.", - "type": "array", - "minItems": 1, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/replacement" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the change.", - "$ref": "#/definitions/propertyBag" - } - - }, - - "required": [ "artifactLocation", "replacements" ] - }, - - "artifactContent": { - "description": "Represents the contents of an artifact.", - "type": "object", - "additionalProperties": false, - "properties": { - - "text": { - "description": "UTF-8-encoded content from a text artifact.", - "type": "string" - }, - - "binary": { - "description": "MIME Base64-encoded content from a binary artifact, or from a text artifact in its original encoding.", - "type": "string" - }, - - "rendered": { - "description": "An alternate rendered representation of the artifact (e.g., a decompiled representation of a binary region).", - "$ref": "#/definitions/multiformatMessageString" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the artifact content.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "artifactLocation": { - "description": "Specifies the location of an artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "uri": { - "description": "A string containing a valid relative or absolute URI.", - "type": "string", - "format": "uri-reference" - }, - - "uriBaseId": { - "description": "A string which indirectly specifies the absolute URI with respect to which a relative URI in the \"uri\" property is interpreted.", - "type": "string" - }, - - "index": { - "description": "The index within the run artifacts array of the artifact object associated with the artifact location.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "description": { - "description": "A short description of the artifact location.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the artifact location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "attachment": { - "description": "An artifact relevant to a result.", - "type": "object", - "additionalProperties": false, - "properties": { - - "description": { - "description": "A message describing the role played by the attachment.", - "$ref": "#/definitions/message" - }, - - "artifactLocation": { - "description": "The location of the attachment.", - "$ref": "#/definitions/artifactLocation" - }, - - "regions": { - "description": "An array of regions of interest within the attachment.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/region" - } - }, - - "rectangles": { - "description": "An array of rectangles specifying areas of interest within the image.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/rectangle" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the attachment.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "artifactLocation" ] - }, - - "codeFlow": { - "description": "A set of threadFlows which together describe a pattern of code execution relevant to detecting a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "message": { - "description": "A message relevant to the code flow.", - "$ref": "#/definitions/message" - }, - - "threadFlows": { - "description": "An array of one or more unique threadFlow objects, each of which describes the progress of a program through a thread of execution.", - "type": "array", - "minItems": 1, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/threadFlow" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the code flow.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "threadFlows" ] - }, - - "configurationOverride": { - "description": "Information about how a specific rule or notification was reconfigured at runtime.", - "type": "object", - "additionalProperties": false, - "properties": { - - "configuration": { - "description": "Specifies how the rule or notification was configured during the scan.", - "$ref": "#/definitions/reportingConfiguration" - }, - - "descriptor": { - "description": "A reference used to locate the descriptor whose configuration was overridden.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the configuration override.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "configuration", "descriptor" ] - }, - - "conversion": { - "description": "Describes how a converter transformed the output of a static analysis tool from the analysis tool's native output format into the SARIF format.", - "additionalProperties": false, - "type": "object", - "properties": { - - "tool": { - "description": "A tool object that describes the converter.", - "$ref": "#/definitions/tool" - }, - - "invocation": { - "description": "An invocation object that describes the invocation of the converter.", - "$ref": "#/definitions/invocation" - }, - - "analysisToolLogFiles": { - "description": "The locations of the analysis tool's per-run log files.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the conversion.", - "$ref": "#/definitions/propertyBag" - } - - }, - - "required": [ "tool" ] - }, - - "edge": { - "description": "Represents a directed edge in a graph.", - "type": "object", - "additionalProperties": false, - "properties": { - - "id": { - "description": "A string that uniquely identifies the edge within its graph.", - "type": "string" - }, - - "label": { - "description": "A short description of the edge.", - "$ref": "#/definitions/message" - }, - - "sourceNodeId": { - "description": "Identifies the source node (the node at which the edge starts).", - "type": "string" - }, - - "targetNodeId": { - "description": "Identifies the target node (the node at which the edge ends).", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the edge.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "id", "sourceNodeId", "targetNodeId" ] - }, - - "edgeTraversal": { - "description": "Represents the traversal of a single edge during a graph traversal.", - "type": "object", - "additionalProperties": false, - "properties": { - - "edgeId": { - "description": "Identifies the edge being traversed.", - "type": "string" - }, - - "message": { - "description": "A message to display to the user as the edge is traversed.", - "$ref": "#/definitions/message" - }, - - "finalState": { - "description": "The values of relevant expressions after the edge has been traversed.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "stepOverEdgeCount": { - "description": "The number of edge traversals necessary to return from a nested graph.", - "type": "integer", - "minimum": 0 - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the edge traversal.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "edgeId" ] - }, - - "exception": { - "description": "Describes a runtime exception encountered during the execution of an analysis tool.", - "type": "object", - "additionalProperties": false, - "properties": { - - "kind": { - "type": "string", - "description": "A string that identifies the kind of exception, for example, the fully qualified type name of an object that was thrown, or the symbolic name of a signal." - }, - - "message": { - "description": "A message that describes the exception.", - "type": "string" - }, - - "stack": { - "description": "The sequence of function calls leading to the exception.", - "$ref": "#/definitions/stack" - }, - - "innerExceptions": { - "description": "An array of exception objects each of which is considered a cause of this exception.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/exception" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the exception.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "externalProperties": { - "description": "The top-level element of an external property file.", - "type": "object", - "additionalProperties": false, - "properties": { - - "schema": { - "description": "The URI of the JSON schema corresponding to the version of the external property file format.", - "type": "string", - "format": "uri" - }, - - "version": { - "description": "The SARIF format version of this external properties object.", - "enum": [ "2.1.0" ], - "type": "string" - }, - - "guid": { - "description": "A stable, unique identifier for this external properties object, in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "runGuid": { - "description": "A stable, unique identifier for the run associated with this external properties object, in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "conversion": { - "description": "A conversion object that will be merged with a separate run.", - "$ref": "#/definitions/conversion" - }, - - "graphs": { - "description": "An array of graph objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "default": [], - "uniqueItems": true, - "items": { - "$ref": "#/definitions/graph" - } - }, - - "externalizedProperties": { - "description": "Key/value pairs that provide additional information that will be merged with a separate run.", - "$ref": "#/definitions/propertyBag" - }, - - "artifacts": { - "description": "An array of artifact objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifact" - } - }, - - "invocations": { - "description": "Describes the invocation of the analysis tool that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/invocation" - } - }, - - "logicalLocations": { - "description": "An array of logical locations such as namespaces, types or functions that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/logicalLocation" - } - }, - - "threadFlowLocations": { - "description": "An array of threadFlowLocation objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/threadFlowLocation" - } - }, - - "results": { - "description": "An array of result objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/result" - } - }, - - "taxonomies": { - "description": "Tool taxonomies that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "driver": { - "description": "The analysis tool object that will be merged with a separate run.", - "$ref": "#/definitions/toolComponent" - }, - - "extensions": { - "description": "Tool extensions that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "policies": { - "description": "Tool policies that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "translations": { - "description": "Tool translations that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "addresses": { - "description": "Addresses that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/address" - } - }, - - "webRequests": { - "description": "Requests that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webRequest" - } - }, - - "webResponses": { - "description": "Responses that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webResponse" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the external properties.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "externalPropertyFileReference": { - "description": "Contains information that enables a SARIF consumer to locate the external property file that contains the value of an externalized property associated with the run.", - "type": "object", - "additionalProperties": false, - "properties": { - - "location": { - "description": "The location of the external property file.", - "$ref": "#/definitions/artifactLocation" - }, - - "guid": { - "description": "A stable, unique identifier for the external property file in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "itemCount": { - "description": "A non-negative integer specifying the number of items contained in the external property file.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the external property file.", - "$ref": "#/definitions/propertyBag" - } - }, - "anyOf": [ - { "required": [ "location" ] }, - { "required": [ "guid" ] } - ] - }, - - "externalPropertyFileReferences": { - "description": "References to external property files that should be inlined with the content of a root log file.", - "additionalProperties": false, - "type": "object", - "properties": { - - "conversion": { - "description": "An external property file containing a run.conversion object to be merged with the root log file.", - "$ref": "#/definitions/externalPropertyFileReference" - }, - - "graphs": { - "description": "An array of external property files containing a run.graphs object to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "externalizedProperties": { - "description": "An external property file containing a run.properties object to be merged with the root log file.", - "$ref": "#/definitions/externalPropertyFileReference" - }, - - "artifacts": { - "description": "An array of external property files containing run.artifacts arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "invocations": { - "description": "An array of external property files containing run.invocations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "logicalLocations": { - "description": "An array of external property files containing run.logicalLocations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "threadFlowLocations": { - "description": "An array of external property files containing run.threadFlowLocations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "results": { - "description": "An array of external property files containing run.results arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "taxonomies": { - "description": "An array of external property files containing run.taxonomies arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "addresses": { - "description": "An array of external property files containing run.addresses arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "driver": { - "description": "An external property file containing a run.driver object to be merged with the root log file.", - "$ref": "#/definitions/externalPropertyFileReference" - }, - - "extensions": { - "description": "An array of external property files containing run.extensions arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "policies": { - "description": "An array of external property files containing run.policies arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "translations": { - "description": "An array of external property files containing run.translations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "webRequests": { - "description": "An array of external property files containing run.requests arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "webResponses": { - "description": "An array of external property files containing run.responses arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the external property files.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "fix": { - "description": "A proposed fix for the problem represented by a result object. A fix specifies a set of artifacts to modify. For each artifact, it specifies a set of bytes to remove, and provides a set of new bytes to replace them.", - "additionalProperties": false, - "type": "object", - "properties": { - - "description": { - "description": "A message that describes the proposed fix, enabling viewers to present the proposed change to an end user.", - "$ref": "#/definitions/message" - }, - - "artifactChanges": { - "description": "One or more artifact changes that comprise a fix for a result.", - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifactChange" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the fix.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "artifactChanges" ] - }, - - "graph": { - "description": "A network of nodes and directed edges that describes some aspect of the structure of the code (for example, a call graph).", - "type": "object", - "additionalProperties": false, - "properties": { - - "description": { - "description": "A description of the graph.", - "$ref": "#/definitions/message" - }, - - "nodes": { - "description": "An array of node objects representing the nodes of the graph.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/node" - } - }, - - "edges": { - "description": "An array of edge objects representing the edges of the graph.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/edge" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the graph.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "graphTraversal": { - "description": "Represents a path through a graph.", - "type": "object", - "additionalProperties": false, - "properties": { - - "runGraphIndex": { - "description": "The index within the run.graphs to be associated with the result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "resultGraphIndex": { - "description": "The index within the result.graphs to be associated with the result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "description": { - "description": "A description of this graph traversal.", - "$ref": "#/definitions/message" - }, - - "initialState": { - "description": "Values of relevant expressions at the start of the graph traversal that may change during graph traversal.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "immutableState": { - "description": "Values of relevant expressions at the start of the graph traversal that remain constant for the graph traversal.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "edgeTraversals": { - "description": "The sequences of edges traversed by this graph traversal.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/edgeTraversal" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the graph traversal.", - "$ref": "#/definitions/propertyBag" - } - }, - "oneOf": [ - { "required": [ "runGraphIndex" ] }, - { "required": [ "resultGraphIndex" ] } - ] - }, - - "invocation": { - "description": "The runtime environment of the analysis tool run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "commandLine": { - "description": "The command line used to invoke the tool.", - "type": "string" - }, - - "arguments": { - "description": "An array of strings, containing in order the command line arguments passed to the tool from the operating system.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "items": { - "type": "string" - } - }, - - "responseFiles": { - "description": "The locations of any response files specified on the tool's command line.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "startTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the invocation started. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "endTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the invocation ended. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "exitCode": { - "description": "The process exit code.", - "type": "integer" - }, - - "ruleConfigurationOverrides": { - "description": "An array of configurationOverride objects that describe rules related runtime overrides.", - "type": "array", - "minItems": 0, - "default": [], - "uniqueItems": true, - "items": { - "$ref": "#/definitions/configurationOverride" - } - }, - - "notificationConfigurationOverrides": { - "description": "An array of configurationOverride objects that describe notifications related runtime overrides.", - "type": "array", - "minItems": 0, - "default": [], - "uniqueItems": true, - "items": { - "$ref": "#/definitions/configurationOverride" - } - }, - - "toolExecutionNotifications": { - "description": "A list of runtime conditions detected by the tool during the analysis.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/notification" - } - }, - - "toolConfigurationNotifications": { - "description": "A list of conditions detected by the tool that are relevant to the tool's configuration.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/notification" - } - }, - - "exitCodeDescription": { - "description": "The reason for the process exit.", - "type": "string" - }, - - "exitSignalName": { - "description": "The name of the signal that caused the process to exit.", - "type": "string" - }, - - "exitSignalNumber": { - "description": "The numeric value of the signal that caused the process to exit.", - "type": "integer" - }, - - "processStartFailureMessage": { - "description": "The reason given by the operating system that the process failed to start.", - "type": "string" - }, - - "executionSuccessful": { - "description": "Specifies whether the tool's execution completed successfully.", - "type": "boolean" - }, - - "machine": { - "description": "The machine on which the invocation occurred.", - "type": "string" - }, - - "account": { - "description": "The account under which the invocation occurred.", - "type": "string" - }, - - "processId": { - "description": "The id of the process in which the invocation occurred.", - "type": "integer" - }, - - "executableLocation": { - "description": "An absolute URI specifying the location of the executable that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "workingDirectory": { - "description": "The working directory for the invocation.", - "$ref": "#/definitions/artifactLocation" - }, - - "environmentVariables": { - "description": "The environment variables associated with the analysis tool process, expressed as key/value pairs.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "stdin": { - "description": "A file containing the standard input stream to the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "stdout": { - "description": "A file containing the standard output stream from the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "stderr": { - "description": "A file containing the standard error stream from the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "stdoutStderr": { - "description": "A file containing the interleaved standard output and standard error stream from the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the invocation.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "executionSuccessful" ] - }, - - "location": { - "description": "A location within a programming artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "id": { - "description": "Value that distinguishes this location from all other locations within a single result object.", - "type": "integer", - "minimum": -1, - "default": -1 - }, - - "physicalLocation": { - "description": "Identifies the artifact and region.", - "$ref": "#/definitions/physicalLocation" - }, - - "logicalLocations": { - "description": "The logical locations associated with the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/logicalLocation" - } - }, - - "message": { - "description": "A message relevant to the location.", - "$ref": "#/definitions/message" - }, - - "annotations": { - "description": "A set of regions relevant to the location.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/region" - } - }, - - "relationships": { - "description": "An array of objects that describe relationships between this location and others.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/locationRelationship" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "locationRelationship": { - "description": "Information about the relation of one location to another.", - "type": "object", - "additionalProperties": false, - "properties": { - - "target": { - "description": "A reference to the related location.", - "type": "integer", - "minimum": 0 - }, - - "kinds": { - "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'includes', 'isIncludedBy' and 'relevant'.", - "type": "array", - "default": [ "relevant" ], - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "description": { - "description": "A description of the location relationship.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the location relationship.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "target" ] - }, - - "logicalLocation": { - "description": "A logical location of a construct that produced a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "name": { - "description": "Identifies the construct in which the result occurred. For example, this property might contain the name of a class or a method.", - "type": "string" - }, - - "index": { - "description": "The index within the logical locations array.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "fullyQualifiedName": { - "description": "The human-readable fully qualified name of the logical location.", - "type": "string" - }, - - "decoratedName": { - "description": "The machine-readable name for the logical location, such as a mangled function name provided by a C++ compiler that encodes calling convention, return type and other details along with the function name.", - "type": "string" - }, - - "parentIndex": { - "description": "Identifies the index of the immediate parent of the construct in which the result was detected. For example, this property might point to a logical location that represents the namespace that holds a type.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "kind": { - "description": "The type of construct this logical location component refers to. Should be one of 'function', 'member', 'module', 'namespace', 'parameter', 'resource', 'returnType', 'type', 'variable', 'object', 'array', 'property', 'value', 'element', 'text', 'attribute', 'comment', 'declaration', 'dtd' or 'processingInstruction', if any of those accurately describe the construct.", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the logical location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "message": { - "description": "Encapsulates a message intended to be read by the end user.", - "type": "object", - "additionalProperties": false, - - "properties": { - - "text": { - "description": "A plain text message string.", - "type": "string" - }, - - "markdown": { - "description": "A Markdown message string.", - "type": "string" - }, - - "id": { - "description": "The identifier for this message.", - "type": "string" - }, - - "arguments": { - "description": "An array of strings to substitute into the message string.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "type": "string" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the message.", - "$ref": "#/definitions/propertyBag" - } - }, - "anyOf": [ - { "required": [ "text" ] }, - { "required": [ "id" ] } - ] - }, - - "multiformatMessageString": { - "description": "A message string or message format string rendered in multiple formats.", - "type": "object", - "additionalProperties": false, - - "properties": { - - "text": { - "description": "A plain text message string or format string.", - "type": "string" - }, - - "markdown": { - "description": "A Markdown message string or format string.", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the message.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "text" ] - }, - - "node": { - "description": "Represents a node in a graph.", - "type": "object", - "additionalProperties": false, - - "properties": { - - "id": { - "description": "A string that uniquely identifies the node within its graph.", - "type": "string" - }, - - "label": { - "description": "A short description of the node.", - "$ref": "#/definitions/message" - }, - - "location": { - "description": "A code location associated with the node.", - "$ref": "#/definitions/location" - }, - - "children": { - "description": "Array of child nodes.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/node" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the node.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "id" ] - }, - - "notification": { - "description": "Describes a condition relevant to the tool itself, as opposed to being relevant to a target being analyzed by the tool.", - "type": "object", - "additionalProperties": false, - "properties": { - - "locations": { - "description": "The locations relevant to this notification.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/location" - } - }, - - "message": { - "description": "A message that describes the condition that was encountered.", - "$ref": "#/definitions/message" - }, - - "level": { - "description": "A value specifying the severity level of the notification.", - "default": "warning", - "enum": [ "none", "note", "warning", "error" ], - "type": "string" - }, - - "threadId": { - "description": "The thread identifier of the code that generated the notification.", - "type": "integer" - }, - - "timeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the analysis tool generated the notification.", - "type": "string", - "format": "date-time" - }, - - "exception": { - "description": "The runtime exception, if any, relevant to this notification.", - "$ref": "#/definitions/exception" - }, - - "descriptor": { - "description": "A reference used to locate the descriptor relevant to this notification.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "associatedRule": { - "description": "A reference used to locate the rule descriptor associated with this notification.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the notification.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "message" ] - }, - - "physicalLocation": { - "description": "A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "address": { - "description": "The address of the location.", - "$ref": "#/definitions/address" - }, - - "artifactLocation": { - "description": "The location of the artifact.", - "$ref": "#/definitions/artifactLocation" - }, - - "region": { - "description": "Specifies a portion of the artifact.", - "$ref": "#/definitions/region" - }, - - "contextRegion": { - "description": "Specifies a portion of the artifact that encloses the region. Allows a viewer to display additional context around the region.", - "$ref": "#/definitions/region" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the physical location.", - "$ref": "#/definitions/propertyBag" - } - }, - - "anyOf": [ - { - "required": [ "address" ] - }, - { - "required": [ "artifactLocation" ] - } - ] - }, - - "propertyBag": { - "description": "Key/value pairs that provide additional information about the object.", - "type": "object", - "additionalProperties": true, - "properties": { - "tags": { - - "description": "A set of distinct strings that provide additional information.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "type": "string" - } - } - } - }, - - "rectangle": { - "description": "An area within an image.", - "additionalProperties": false, - "type": "object", - "properties": { - - "top": { - "description": "The Y coordinate of the top edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "left": { - "description": "The X coordinate of the left edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "bottom": { - "description": "The Y coordinate of the bottom edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "right": { - "description": "The X coordinate of the right edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "message": { - "description": "A message relevant to the rectangle.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the rectangle.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "region": { - "description": "A region within an artifact where a result was detected.", - "additionalProperties": false, - "type": "object", - "properties": { - - "startLine": { - "description": "The line number of the first character in the region.", - "type": "integer", - "minimum": 1 - }, - - "startColumn": { - "description": "The column number of the first character in the region.", - "type": "integer", - "minimum": 1 - }, - - "endLine": { - "description": "The line number of the last character in the region.", - "type": "integer", - "minimum": 1 - }, - - "endColumn": { - "description": "The column number of the character following the end of the region.", - "type": "integer", - "minimum": 1 - }, - - "charOffset": { - "description": "The zero-based offset from the beginning of the artifact of the first character in the region.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "charLength": { - "description": "The length of the region in characters.", - "type": "integer", - "minimum": 0 - }, - - "byteOffset": { - "description": "The zero-based offset from the beginning of the artifact of the first byte in the region.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "byteLength": { - "description": "The length of the region in bytes.", - "type": "integer", - "minimum": 0 - }, - - "snippet": { - "description": "The portion of the artifact contents within the specified region.", - "$ref": "#/definitions/artifactContent" - }, - - "message": { - "description": "A message relevant to the region.", - "$ref": "#/definitions/message" - }, - - "sourceLanguage": { - "description": "Specifies the source language, if any, of the portion of the artifact specified by the region object.", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the region.", - "$ref": "#/definitions/propertyBag" - } - }, - - "anyOf": [ - { "required": [ "startLine" ] }, - { "required": [ "charOffset" ] }, - { "required": [ "byteOffset" ] } - ] - }, - - "replacement": { - "description": "The replacement of a single region of an artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "deletedRegion": { - "description": "The region of the artifact to delete.", - "$ref": "#/definitions/region" - }, - - "insertedContent": { - "description": "The content to insert at the location specified by the 'deletedRegion' property.", - "$ref": "#/definitions/artifactContent" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the replacement.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "deletedRegion" ] - }, - - "reportingDescriptor": { - "description": "Metadata that describes a specific report produced by the tool, as part of the analysis it provides or its runtime reporting.", - "additionalProperties": false, - "type": "object", - "properties": { - - "id": { - "description": "A stable, opaque identifier for the report.", - "type": "string" - }, - - "deprecatedIds": { - "description": "An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "guid": { - "description": "A unique identifier for the reporting descriptor in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "deprecatedGuids": { - "description": "An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - } - }, - - "name": { - "description": "A report identifier that is understandable to an end user.", - "type": "string" - }, - - "deprecatedNames": { - "description": "An array of readable identifiers by which this report was known in some previous version of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "shortDescription": { - "description": "A concise description of the report. Should be a single sentence that is understandable when visible space is limited to a single line of text.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullDescription": { - "description": "A description of the report. Should, as far as possible, provide details sufficient to enable resolution of any problem indicated by the result.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "messageStrings": { - "description": "A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "defaultConfiguration": { - "description": "Default reporting configuration information.", - "$ref": "#/definitions/reportingConfiguration" - }, - - "helpUri": { - "description": "A URI where the primary documentation for the report can be found.", - "type": "string", - "format": "uri" - }, - - "help": { - "description": "Provides the primary documentation for the report, useful when there is no online documentation.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "relationships": { - "description": "An array of objects that describe relationships between this reporting descriptor and others.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/reportingDescriptorRelationship" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the report.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "id" ] - }, - - "reportingConfiguration": { - "description": "Information about a rule or notification that can be configured at runtime.", - "type": "object", - "additionalProperties": false, - "properties": { - - "enabled": { - "description": "Specifies whether the report may be produced during the scan.", - "type": "boolean", - "default": true - }, - - "level": { - "description": "Specifies the failure level for the report.", - "default": "warning", - "enum": [ "none", "note", "warning", "error" ], - "type": "string" - }, - - "rank": { - "description": "Specifies the relative priority of the report. Used for analysis output only.", - "type": "number", - "default": -1.0, - "minimum": -1.0, - "maximum": 100.0 - }, - - "parameters": { - "description": "Contains configuration information specific to a report.", - "$ref": "#/definitions/propertyBag" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the reporting configuration.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "reportingDescriptorReference": { - "description": "Information about how to locate a relevant reporting descriptor.", - "type": "object", - "additionalProperties": false, - "properties": { - - "id": { - "description": "The id of the descriptor.", - "type": "string" - }, - - "index": { - "description": "The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "guid": { - "description": "A guid that uniquely identifies the descriptor.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "toolComponent": { - "description": "A reference used to locate the toolComponent associated with the descriptor.", - "$ref": "#/definitions/toolComponentReference" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", - "$ref": "#/definitions/propertyBag" - } - }, - "anyOf": [ - { "required": [ "index" ] }, - { "required": [ "guid" ] }, - { "required": [ "id" ] } - ] - }, - - "reportingDescriptorRelationship": { - "description": "Information about the relation of one reporting descriptor to another.", - "type": "object", - "additionalProperties": false, - "properties": { - - "target": { - "description": "A reference to the related reporting descriptor.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "kinds": { - "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'canPrecede', 'canFollow', 'willPrecede', 'willFollow', 'superset', 'subset', 'equal', 'disjoint', 'relevant', and 'incomparable'.", - "type": "array", - "default": [ "relevant" ], - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "description": { - "description": "A description of the reporting descriptor relationship.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "target" ] - }, - - "result": { - "description": "A result produced by an analysis tool.", - "additionalProperties": false, - "type": "object", - "properties": { - - "ruleId": { - "description": "The stable, unique identifier of the rule, if any, to which this result is relevant.", - "type": "string" - }, - - "ruleIndex": { - "description": "The index within the tool component rules array of the rule object associated with this result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "rule": { - "description": "A reference used to locate the rule descriptor relevant to this result.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "kind": { - "description": "A value that categorizes results by evaluation state.", - "default": "fail", - "enum": [ "notApplicable", "pass", "fail", "review", "open", "informational" ], - "type": "string" - }, - - "level": { - "description": "A value specifying the severity level of the result.", - "default": "warning", - "enum": [ "none", "note", "warning", "error" ], - "type": "string" - }, - - "message": { - "description": "A message that describes the result. The first sentence of the message only will be displayed when visible space is limited.", - "$ref": "#/definitions/message" - }, - - "analysisTarget": { - "description": "Identifies the artifact that the analysis tool was instructed to scan. This need not be the same as the artifact where the result actually occurred.", - "$ref": "#/definitions/artifactLocation" - }, - - "locations": { - "description": "The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every specified location.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/location" - } - }, - - "guid": { - "description": "A stable, unique identifier for the result in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "correlationGuid": { - "description": "A stable, unique identifier for the equivalence class of logically identical results to which this result belongs, in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "occurrenceCount": { - "description": "A positive integer specifying the number of times this logically unique result was observed in this run.", - "type": "integer", - "minimum": 1 - }, - - "partialFingerprints": { - "description": "A set of strings that contribute to the stable, unique identity of the result.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "fingerprints": { - "description": "A set of strings each of which individually defines a stable, unique identity for the result.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "stacks": { - "description": "An array of 'stack' objects relevant to the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/stack" - } - }, - - "codeFlows": { - "description": "An array of 'codeFlow' objects relevant to the result.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/codeFlow" - } - }, - - "graphs": { - "description": "An array of zero or more unique graph objects associated with the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/graph" - } - }, - - "graphTraversals": { - "description": "An array of one or more unique 'graphTraversal' objects.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/graphTraversal" - } - }, - - "relatedLocations": { - "description": "A set of locations relevant to this result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/location" - } - }, - - "suppressions": { - "description": "A set of suppressions relevant to this result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/suppression" - } - }, - - "baselineState": { - "description": "The state of a result relative to a baseline of a previous run.", - "enum": [ - "new", - "unchanged", - "updated", - "absent" - ], - "type": "string" - }, - - "rank": { - "description": "A number representing the priority or importance of the result.", - "type": "number", - "default": -1.0, - "minimum": -1.0, - "maximum": 100.0 - }, - - "attachments": { - "description": "A set of artifacts relevant to the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/attachment" - } - }, - - "hostedViewerUri": { - "description": "An absolute URI at which the result can be viewed.", - "type": "string", - "format": "uri" - }, - - "workItemUris": { - "description": "The URIs of the work items associated with this result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string", - "format": "uri" - } - }, - - "provenance": { - "description": "Information about how and when the result was detected.", - "$ref": "#/definitions/resultProvenance" - }, - - "fixes": { - "description": "An array of 'fix' objects, each of which represents a proposed fix to the problem indicated by the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/fix" - } - }, - - "taxa": { - "description": "An array of references to taxonomy reporting descriptors that are applicable to the result.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/reportingDescriptorReference" - } - }, - - "webRequest": { - "description": "A web request associated with this result.", - "$ref": "#/definitions/webRequest" - }, - - "webResponse": { - "description": "A web response associated with this result.", - "$ref": "#/definitions/webResponse" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the result.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "message" ] - }, - - "resultProvenance": { - "description": "Contains information about how and when a result was detected.", - "additionalProperties": false, - "type": "object", - "properties": { - - "firstDetectionTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the result was first detected. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "lastDetectionTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the result was most recently detected. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "firstDetectionRunGuid": { - "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was first detected.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "lastDetectionRunGuid": { - "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was most recently detected.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "invocationIndex": { - "description": "The index within the run.invocations array of the invocation object which describes the tool invocation that detected the result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "conversionSources": { - "description": "An array of physicalLocation objects which specify the portions of an analysis tool's output that a converter transformed into the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/physicalLocation" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the result.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "run": { - "description": "Describes a single run of an analysis tool, and contains the reported output of that run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "tool": { - "description": "Information about the tool or tool pipeline that generated the results in this run. A run can only contain results produced by a single tool or tool pipeline. A run can aggregate results from multiple log files, as long as context around the tool run (tool command-line arguments and the like) is identical for all aggregated files.", - "$ref": "#/definitions/tool" - }, - - "invocations": { - "description": "Describes the invocation of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/invocation" - } - }, - - "conversion": { - "description": "A conversion object that describes how a converter transformed an analysis tool's native reporting format into the SARIF format.", - "$ref": "#/definitions/conversion" - }, - - "language": { - "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase culture code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", - "type": "string", - "default": "en-US", - "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" - }, - - "versionControlProvenance": { - "description": "Specifies the revision in version control of the artifacts that were scanned.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/versionControlDetails" - } - }, - - "originalUriBaseIds": { - "description": "The artifact location specified by each uriBaseId symbol on the machine where the tool originally ran.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "artifacts": { - "description": "An array of artifact objects relevant to the run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifact" - } - }, - - "logicalLocations": { - "description": "An array of logical locations such as namespaces, types or functions.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/logicalLocation" - } - }, - - "graphs": { - "description": "An array of zero or more unique graph objects associated with the run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/graph" - } - }, - - "results": { - "description": "The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be empty) if a log file represents an actual scan.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/result" - } - }, - - "automationDetails": { - "description": "Automation details that describe this run.", - "$ref": "#/definitions/runAutomationDetails" - }, - - "runAggregates": { - "description": "Automation details that describe the aggregate of runs to which this run belongs.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/runAutomationDetails" - } - }, - - "baselineGuid": { - "description": "The 'guid' property of a previous SARIF 'run' that comprises the baseline that was used to compute result 'baselineState' properties for the run.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "redactionTokens": { - "description": "An array of strings used to replace sensitive information in a redaction-aware property.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "type": "string" - } - }, - - "defaultEncoding": { - "description": "Specifies the default encoding for any artifact object that refers to a text file.", - "type": "string" - }, - - "defaultSourceLanguage": { - "description": "Specifies the default source language for any artifact object that refers to a text file that contains source code.", - "type": "string" - }, - - "newlineSequences": { - "description": "An ordered list of character sequences that were treated as line breaks when computing region information for the run.", - "type": "array", - "minItems": 1, - "uniqueItems": true, - "default": [ "\r\n", "\n" ], - "items": { - "type": "string" - } - }, - - "columnKind": { - "description": "Specifies the unit in which the tool measures columns.", - "enum": [ "utf16CodeUnits", "unicodeCodePoints" ], - "type": "string" - }, - - "externalPropertyFileReferences": { - "description": "References to external property files that should be inlined with the content of a root log file.", - "$ref": "#/definitions/externalPropertyFileReferences" - }, - - "threadFlowLocations": { - "description": "An array of threadFlowLocation objects cached at run level.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/threadFlowLocation" - } - }, - - "taxonomies": { - "description": "An array of toolComponent objects relevant to a taxonomy in which results are categorized.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "addresses": { - "description": "Addresses associated with this run instance, if any.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/address" - } - }, - - "translations": { - "description": "The set of available translations of the localized data provided by the tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "policies": { - "description": "Contains configurations that may potentially override both reportingDescriptor.defaultConfiguration (the tool's default severities) and invocation.configurationOverrides (severities established at run-time from the command line).", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "webRequests": { - "description": "An array of request objects cached at run level.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webRequest" - } - }, - - "webResponses": { - "description": "An array of response objects cached at run level.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webResponse" - } - }, - - "specialLocations": { - "description": "A specialLocations object that defines locations of special significance to SARIF consumers.", - "$ref": "#/definitions/specialLocations" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the run.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "tool" ] - }, - - "runAutomationDetails": { - "description": "Information that describes a run's identity and role within an engineering system process.", - "additionalProperties": false, - "type": "object", - "properties": { - - "description": { - "description": "A description of the identity and role played within the engineering system by this object's containing run object.", - "$ref": "#/definitions/message" - }, - - "id": { - "description": "A hierarchical string that uniquely identifies this object's containing run object.", - "type": "string" - }, - - "guid": { - "description": "A stable, unique identifier for this object's containing run object in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "correlationGuid": { - "description": "A stable, unique identifier for the equivalence class of runs to which this object's containing run object belongs in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the run automation details.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "specialLocations": { - "description": "Defines locations of special significance to SARIF consumers.", - "type": "object", - "additionalProperties": false, - "properties": { - - "displayBase": { - "description": "Provides a suggestion to SARIF consumers to display file paths relative to the specified location.", - "$ref": "#/definitions/artifactLocation" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the special locations.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "stack": { - "description": "A call stack that is relevant to a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "message": { - "description": "A message relevant to this call stack.", - "$ref": "#/definitions/message" - }, - - "frames": { - "description": "An array of stack frames that represents a sequence of calls, rendered in reverse chronological order, that comprise the call stack.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/stackFrame" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the stack.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "frames" ] - }, - - "stackFrame": { - "description": "A function call within a stack trace.", - "additionalProperties": false, - "type": "object", - "properties": { - - "location": { - "description": "The location to which this stack frame refers.", - "$ref": "#/definitions/location" - }, - - "module": { - "description": "The name of the module that contains the code of this stack frame.", - "type": "string" - }, - - "threadId": { - "description": "The thread identifier of the stack frame.", - "type": "integer" - }, - - "parameters": { - "description": "The parameters of the call that is executing.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "type": "string", - "default": [] - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the stack frame.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "suppression": { - "description": "A suppression that is relevant to a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "guid": { - "description": "A stable, unique identifier for the suprression in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "kind": { - "description": "A string that indicates where the suppression is persisted.", - "enum": [ - "inSource", - "external" - ], - "type": "string" - }, - - "status": { - "description": "A string that indicates the review status of the suppression.", - "enum": [ - "accepted", - "underReview", - "rejected" - ], - "type": "string" - }, - - "justification": { - "description": "A string representing the justification for the suppression.", - "type": "string" - }, - - "location": { - "description": "Identifies the location associated with the suppression.", - "$ref": "#/definitions/location" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the suppression.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "kind" ] - }, - - "threadFlow": { - "description": "Describes a sequence of code locations that specify a path through a single thread of execution such as an operating system or fiber.", - "type": "object", - "additionalProperties": false, - "properties": { - - "id": { - "description": "An string that uniquely identifies the threadFlow within the codeFlow in which it occurs.", - "type": "string" - }, - - "message": { - "description": "A message relevant to the thread flow.", - "$ref": "#/definitions/message" - }, - - - "initialState": { - "description": "Values of relevant expressions at the start of the thread flow that may change during thread flow execution.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "immutableState": { - "description": "Values of relevant expressions at the start of the thread flow that remain constant.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "locations": { - "description": "A temporally ordered array of 'threadFlowLocation' objects, each of which describes a location visited by the tool while producing the result.", - "type": "array", - "minItems": 1, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/threadFlowLocation" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the thread flow.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "locations" ] - }, - - "threadFlowLocation": { - "description": "A location visited by an analysis tool while simulating or monitoring the execution of a program.", - "additionalProperties": false, - "type": "object", - "properties": { - - "index": { - "description": "The index within the run threadFlowLocations array.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "location": { - "description": "The code location.", - "$ref": "#/definitions/location" - }, - - "stack": { - "description": "The call stack leading to this location.", - "$ref": "#/definitions/stack" - }, - - "kinds": { - "description": "A set of distinct strings that categorize the thread flow location. Well-known kinds include 'acquire', 'release', 'enter', 'exit', 'call', 'return', 'branch', 'implicit', 'false', 'true', 'caution', 'danger', 'unknown', 'unreachable', 'taint', 'function', 'handler', 'lock', 'memory', 'resource', 'scope' and 'value'.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "type": "string" - } - }, - - "taxa": { - "description": "An array of references to rule or taxonomy reporting descriptors that are applicable to the thread flow location.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/reportingDescriptorReference" - } - }, - - "module": { - "description": "The name of the module that contains the code that is executing.", - "type": "string" - }, - - "state": { - "description": "A dictionary, each of whose keys specifies a variable or expression, the associated value of which represents the variable or expression value. For an annotation of kind 'continuation', for example, this dictionary might hold the current assumed values of a set of global variables.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "nestingLevel": { - "description": "An integer representing a containment hierarchy within the thread flow.", - "type": "integer", - "minimum": 0 - }, - - "executionOrder": { - "description": "An integer representing the temporal order in which execution reached this location.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "executionTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which this location was executed.", - "type": "string", - "format": "date-time" - }, - - "importance": { - "description": "Specifies the importance of this location in understanding the code flow in which it occurs. The order from most to least important is \"essential\", \"important\", \"unimportant\". Default: \"important\".", - "enum": [ "important", "essential", "unimportant" ], - "default": "important", - "type": "string" - }, - - "webRequest": { - "description": "A web request associated with this thread flow location.", - "$ref": "#/definitions/webRequest" - }, - - "webResponse": { - "description": "A web response associated with this thread flow location.", - "$ref": "#/definitions/webResponse" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the threadflow location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "tool": { - "description": "The analysis tool that was run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "driver": { - "description": "The analysis tool that was run.", - "$ref": "#/definitions/toolComponent" - }, - - "extensions": { - "description": "Tool extensions that contributed to or reconfigured the analysis tool that was run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the tool.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "driver" ] - }, - - "toolComponent": { - "description": "A component, such as a plug-in or the driver, of the analysis tool that was run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "guid": { - "description": "A unique identifier for the tool component in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "name": { - "description": "The name of the tool component.", - "type": "string" - }, - - "organization": { - "description": "The organization or company that produced the tool component.", - "type": "string" - }, - - "product": { - "description": "A product suite to which the tool component belongs.", - "type": "string" - }, - - "productSuite": { - "description": "A localizable string containing the name of the suite of products to which the tool component belongs.", - "type": "string" - }, - - "shortDescription": { - "description": "A brief description of the tool component.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullDescription": { - "description": "A comprehensive description of the tool component.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullName": { - "description": "The name of the tool component along with its version and any other useful identifying information, such as its locale.", - "type": "string" - }, - - "version": { - "description": "The tool component version, in whatever format the component natively provides.", - "type": "string" - }, - - "semanticVersion": { - "description": "The tool component version in the format specified by Semantic Versioning 2.0.", - "type": "string" - }, - - "dottedQuadFileVersion": { - "description": "The binary version of the tool component's primary executable file expressed as four non-negative integers separated by a period (for operating systems that express file versions in this way).", - "type": "string", - "pattern": "[0-9]+(\\.[0-9]+){3}" - }, - - "releaseDateUtc": { - "description": "A string specifying the UTC date (and optionally, the time) of the component's release.", - "type": "string" - }, - - "downloadUri": { - "description": "The absolute URI from which the tool component can be downloaded.", - "type": "string", - "format": "uri" - }, - - "informationUri": { - "description": "The absolute URI at which information about this version of the tool component can be found.", - "type": "string", - "format": "uri" - }, - - "globalMessageStrings": { - "description": "A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "notifications": { - "description": "An array of reportingDescriptor objects relevant to the notifications related to the configuration and runtime execution of the tool component.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/reportingDescriptor" - } - }, - - "rules": { - "description": "An array of reportingDescriptor objects relevant to the analysis performed by the tool component.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/reportingDescriptor" - } - }, - - "taxa": { - "description": "An array of reportingDescriptor objects relevant to the definitions of both standalone and tool-defined taxonomies.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/reportingDescriptor" - } - }, - - "locations": { - "description": "An array of the artifactLocation objects associated with the tool component.", - "type": "array", - "minItems": 0, - "default": [], - "items": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "language": { - "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase language code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", - "type": "string", - "default": "en-US", - "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" - }, - - "contents": { - "description": "The kinds of data contained in this object.", - "type": "array", - "uniqueItems": true, - "default": [ "localizedData", "nonLocalizedData" ], - "items": { - "enum": [ - "localizedData", - "nonLocalizedData" - ], - "type": "string" - } - }, - - "isComprehensive": { - "description": "Specifies whether this object contains a complete definition of the localizable and/or non-localizable data for this component, as opposed to including only data that is relevant to the results persisted to this log file.", - "type": "boolean", - "default": false - }, - - "localizedDataSemanticVersion": { - "description": "The semantic version of the localized strings defined in this component; maintained by components that provide translations.", - "type": "string" - }, - - "minimumRequiredLocalizedDataSemanticVersion": { - "description": "The minimum value of localizedDataSemanticVersion required in translations consumed by this component; used by components that consume translations.", - "type": "string" - }, - - "associatedComponent": { - "description": "The component which is strongly associated with this component. For a translation, this refers to the component which has been translated. For an extension, this is the driver that provides the extension's plugin model.", - "$ref": "#/definitions/toolComponentReference" - }, - - "translationMetadata": { - "description": "Translation metadata, required for a translation, not populated by other component types.", - "$ref": "#/definitions/translationMetadata" - }, - - "supportedTaxonomies": { - "description": "An array of toolComponentReference objects to declare the taxonomies supported by the tool component.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponentReference" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the tool component.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "name" ] - }, - - "toolComponentReference": { - "description": "Identifies a particular toolComponent object, either the driver or an extension.", - "type": "object", - "additionalProperties": false, - "properties": { - - "name": { - "description": "The 'name' property of the referenced toolComponent.", - "type": "string" - }, - - "index": { - "description": "An index into the referenced toolComponent in tool.extensions.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "guid": { - "description": "The 'guid' property of the referenced toolComponent.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the toolComponentReference.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "translationMetadata": { - "description": "Provides additional metadata related to translation.", - "type": "object", - "additionalProperties": false, - "properties": { - - "name": { - "description": "The name associated with the translation metadata.", - "type": "string" - }, - - "fullName": { - "description": "The full name associated with the translation metadata.", - "type": "string" - }, - - "shortDescription": { - "description": "A brief description of the translation metadata.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullDescription": { - "description": "A comprehensive description of the translation metadata.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "downloadUri": { - "description": "The absolute URI from which the translation metadata can be downloaded.", - "type": "string", - "format": "uri" - }, - - "informationUri": { - "description": "The absolute URI from which information related to the translation metadata can be downloaded.", - "type": "string", - "format": "uri" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the translation metadata.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "name" ] - }, - - "versionControlDetails": { - "description": "Specifies the information necessary to retrieve a desired revision from a version control system.", - "type": "object", - "additionalProperties": false, - "properties": { - - "repositoryUri": { - "description": "The absolute URI of the repository.", - "type": "string", - "format": "uri" - }, - - "revisionId": { - "description": "A string that uniquely and permanently identifies the revision within the repository.", - "type": "string" - }, - - "branch": { - "description": "The name of a branch containing the revision.", - "type": "string" - }, - - "revisionTag": { - "description": "A tag that has been applied to the revision.", - "type": "string" - }, - - "asOfTimeUtc": { - "description": "A Coordinated Universal Time (UTC) date and time that can be used to synchronize an enlistment to the state of the repository at that time.", - "type": "string", - "format": "date-time" - }, - - "mappedTo": { - "description": "The location in the local file system to which the root of the repository was mapped at the time of the analysis.", - "$ref": "#/definitions/artifactLocation" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the version control details.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "repositoryUri" ] - }, - - "webRequest": { - "description": "Describes an HTTP request.", - "type": "object", - "additionalProperties": false, - "properties": { - - "index": { - "description": "The index within the run.webRequests array of the request object associated with this result.", - "type": "integer", - "default": -1, - "minimum": -1 - - }, - - "protocol": { - "description": "The request protocol. Example: 'http'.", - "type": "string" - }, - - "version": { - "description": "The request version. Example: '1.1'.", - "type": "string" - }, - - "target": { - "description": "The target of the request.", - "type": "string" - }, - - "method": { - "description": "The HTTP method. Well-known values are 'GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'.", - "type": "string" - }, - - "headers": { - "description": "The request headers.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "parameters": { - "description": "The request parameters.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "body": { - "description": "The body of the request.", - "$ref": "#/definitions/artifactContent" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the request.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "webResponse": { - "description": "Describes the response to an HTTP request.", - "type": "object", - "additionalProperties": false, - "properties": { - - "index": { - "description": "The index within the run.webResponses array of the response object associated with this result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "protocol": { - "description": "The response protocol. Example: 'http'.", - "type": "string" - }, - - "version": { - "description": "The response version. Example: '1.1'.", - "type": "string" - }, - - "statusCode": { - "description": "The response status code. Example: 451.", - "type": "integer" - }, - - "reasonPhrase": { - "description": "The response reason. Example: 'Not found'.", - "type": "string" - }, - - "headers": { - "description": "The response headers.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "body": { - "description": "The body of the response.", - "$ref": "#/definitions/artifactContent" - }, - - "noResponseReceived": { - "description": "Specifies whether a response was received from the server.", - "type": "boolean", - "default": false - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the response.", - "$ref": "#/definitions/propertyBag" - } - } - } - } -} diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java index a8ddb700bcc2..a84df20165d7 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/localci/scaparser/strategy/sarif/SarifParserTest.java @@ -18,7 +18,7 @@ static class FullDescriptionCategorizer implements RuleCategorizer { @Override public String categorizeRule(ReportingDescriptor rule) { - return rule.getFullDescription().orElseThrow().getText(); + return rule.getOptionalFullDescription().orElseThrow().text(); } } From 218e9719e8fe544094d269afa1b3502e3b3cf91c Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Sun, 1 Dec 2024 10:17:56 +0100 Subject: [PATCH 19/23] Update docker image version --- src/main/resources/config/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index e0dff938d653..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:pr-3" + default: "ls1tum/artemis-python-docker:v1.1.0" c: # possible overrides: gcc, fact default: "ls1tum/artemis-c-docker:v1.0.0" From cc0e9a301a3b2576a2d78af30d2e96e5c6f6a482 Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Sun, 1 Dec 2024 10:41:55 +0100 Subject: [PATCH 20/23] Add lint selection in config file --- .../templates/aeolus/python/default_static.sh | 2 +- .../aeolus/python/default_static.yaml | 2 +- .../templates/python/test/ruff-student.toml | 35 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/templates/python/test/ruff-student.toml diff --git a/src/main/resources/templates/aeolus/python/default_static.sh b/src/main/resources/templates/aeolus/python/default_static.sh index bdda9592acd3..a60edea08cb3 100644 --- a/src/main/resources/templates/aeolus/python/default_static.sh +++ b/src/main/resources/templates/aeolus/python/default_static.sh @@ -3,7 +3,7 @@ set -e export AEOLUS_INITIAL_DIRECTORY=${PWD} static_code_analysis () { echo '⚙️ executing static_code_analysis' - ruff check --output-format=sarif --output-file=ruff.sarif --exit-zero --select=ALL "${studentParentWorkingDirectoryName}" + ruff check --config=ruff-student.toml --output-format=sarif --output-file=ruff.sarif --exit-zero "${studentParentWorkingDirectoryName}" } build_and_test_the_code () { diff --git a/src/main/resources/templates/aeolus/python/default_static.yaml b/src/main/resources/templates/aeolus/python/default_static.yaml index c67c45e5f9d7..b139cc84ffe8 100644 --- a/src/main/resources/templates/aeolus/python/default_static.yaml +++ b/src/main/resources/templates/aeolus/python/default_static.yaml @@ -1,7 +1,7 @@ api: v0.0.1 actions: - name: static_code_analysis - script: ruff check --output-format=sarif --output-file=ruff.sarif --exit-zero --select=ALL "${studentParentWorkingDirectoryName}" + script: ruff check --config=ruff-student.toml --output-format=sarif --output-file=ruff.sarif --exit-zero "${studentParentWorkingDirectoryName}" results: - name: ruff path: ruff.sarif diff --git a/src/main/resources/templates/python/test/ruff-student.toml b/src/main/resources/templates/python/test/ruff-student.toml new file mode 100644 index 000000000000..9601c7eb09b0 --- /dev/null +++ b/src/main/resources/templates/python/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", +] From 68b69a3d8c17f9fccc42b7953a4a2a0d7e5a6f9d Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Thu, 5 Dec 2024 22:56:19 +0100 Subject: [PATCH 21/23] Enable static code analysis specific files --- .../ProgrammingExerciseRepositoryService.java | 34 ++++++++++++++----- .../test/ruff-student.toml | 0 2 files changed, 26 insertions(+), 8 deletions(-) rename src/main/resources/templates/python/{ => staticCodeAnalysis}/test/ruff-student.toml (100%) 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/resources/templates/python/test/ruff-student.toml b/src/main/resources/templates/python/staticCodeAnalysis/test/ruff-student.toml similarity index 100% rename from src/main/resources/templates/python/test/ruff-student.toml rename to src/main/resources/templates/python/staticCodeAnalysis/test/ruff-student.toml From 7cc3748b05396fbccdf9c19dc1d7e55b907a9b1c Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Thu, 5 Dec 2024 23:04:24 +0100 Subject: [PATCH 22/23] Simplify test case --- .../static-code-analysis/expected/ruff.json | 426 +------ .../static-code-analysis/reports/ruff.sarif | 1086 +---------------- 2 files changed, 34 insertions(+), 1478 deletions(-) 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 index 62b110b1c4fd..d81f53f8106b 100644 --- a/src/test/resources/test-data/static-code-analysis/expected/ruff.json +++ b/src/test/resources/test-data/static-code-analysis/expected/ruff.json @@ -2,51 +2,7 @@ "tool": "RUFF", "issues": [ { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", - "startLine": 1, - "endLine": 1, - "startColumn": 1, - "endColumn": 1, - "rule": "D100", - "category": "pydocstyle", - "message": "Missing docstring in public module", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", - "startLine": 1, - "endLine": 1, - "startColumn": 7, - "endColumn": 14, - "rule": "D101", - "category": "pydocstyle", - "message": "Missing docstring in public class", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", - "startLine": 5, - "endLine": 5, - "startColumn": 9, - "endColumn": 13, - "rule": "ANN201", - "category": "flake8-annotations", - "message": "Missing return type annotation for public function `sort`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/context.py", - "startLine": 5, - "endLine": 5, - "startColumn": 9, - "endColumn": 13, - "rule": "D102", - "category": "pydocstyle", - "message": "Missing docstring in public method", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "filePath": "/usr/local/src/exercise/policy.py", "startLine": 1, "endLine": 1, "startColumn": 1, @@ -57,84 +13,7 @@ "priority": "error" }, { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 1, - "endLine": 1, - "startColumn": 1, - "endColumn": 1, - "rule": "D100", - "category": "pydocstyle", - "message": "Missing docstring in public module", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 4, - "endLine": 4, - "startColumn": 7, - "endColumn": 13, - "rule": "D101", - "category": "pydocstyle", - "message": "Missing docstring in public class", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 7, - "endLine": 7, - "startColumn": 9, - "endColumn": 17, - "rule": "ANN204", - "category": "flake8-annotations", - "message": "Missing return type annotation for special method `__init__`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 7, - "endLine": 7, - "startColumn": 9, - "endColumn": 17, - "rule": "D107", - "category": "pydocstyle", - "message": "Missing docstring in `__init__`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 7, - "endLine": 7, - "startColumn": 24, - "endColumn": 31, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `context`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 10, - "endLine": 10, - "startColumn": 9, - "endColumn": 18, - "rule": "ANN201", - "category": "flake8-annotations", - "message": "Missing return type annotation for public function `configure`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 10, - "endLine": 10, - "startColumn": 9, - "endColumn": 18, - "rule": "D102", - "category": "pydocstyle", - "message": "Missing docstring in public method", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "filePath": "/usr/local/src/exercise/policy.py", "startLine": 11, "endLine": 11, "startColumn": 40, @@ -145,29 +24,7 @@ "priority": "error" }, { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 12, - "endLine": 12, - "startColumn": 13, - "endColumn": 18, - "rule": "T201", - "category": "flake8-print", - "message": "`print` found", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 12, - "endLine": 12, - "startColumn": 19, - "endColumn": 63, - "rule": "Q000", - "category": "flake8-quotes", - "message": "Single quotes found but double quotes preferred", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "filePath": "/usr/local/src/exercise/policy.py", "startLine": 13, "endLine": 13, "startColumn": 46, @@ -178,29 +35,7 @@ "priority": "error" }, { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 15, - "endLine": 15, - "startColumn": 13, - "endColumn": 18, - "rule": "T201", - "category": "flake8-print", - "message": "`print` found", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", - "startLine": 15, - "endLine": 15, - "startColumn": 19, - "endColumn": 73, - "rule": "Q000", - "category": "flake8-quotes", - "message": "Single quotes found but double quotes preferred", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py", + "filePath": "/usr/local/src/exercise/policy.py", "startLine": 16, "endLine": 16, "startColumn": 46, @@ -209,259 +44,6 @@ "category": "Pyflakes", "message": "`BubbleSort` may be undefined, or defined from star imports", "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", - "startLine": 1, - "endLine": 1, - "startColumn": 1, - "endColumn": 1, - "rule": "D100", - "category": "pydocstyle", - "message": "Missing docstring in public module", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", - "startLine": 4, - "endLine": 4, - "startColumn": 7, - "endColumn": 19, - "rule": "D101", - "category": "pydocstyle", - "message": "Missing docstring in public class", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", - "startLine": 7, - "endLine": 7, - "startColumn": 9, - "endColumn": 21, - "rule": "ANN201", - "category": "flake8-annotations", - "message": "Missing return type annotation for public function `perform_sort`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", - "startLine": 7, - "endLine": 7, - "startColumn": 9, - "endColumn": 21, - "rule": "D102", - "category": "pydocstyle", - "message": "Missing docstring in public method", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py", - "startLine": 7, - "endLine": 7, - "startColumn": 28, - "endColumn": 33, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `array`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 1, - "endLine": 1, - "startColumn": 1, - "endColumn": 1, - "rule": "D100", - "category": "pydocstyle", - "message": "Missing docstring in public module", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 4, - "endLine": 4, - "startColumn": 7, - "endColumn": 17, - "rule": "D101", - "category": "pydocstyle", - "message": "Missing docstring in public class", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 6, - "endLine": 6, - "startColumn": 9, - "endColumn": 21, - "rule": "ANN201", - "category": "flake8-annotations", - "message": "Missing return type annotation for public function `perform_sort`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 6, - "endLine": 6, - "startColumn": 9, - "endColumn": 21, - "rule": "D102", - "category": "pydocstyle", - "message": "Missing docstring in public method", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 6, - "endLine": 6, - "startColumn": 28, - "endColumn": 31, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `arr`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 16, - "endLine": 16, - "startColumn": 7, - "endColumn": 16, - "rule": "D101", - "category": "pydocstyle", - "message": "Missing docstring in public class", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 18, - "endLine": 18, - "startColumn": 9, - "endColumn": 21, - "rule": "ANN201", - "category": "flake8-annotations", - "message": "Missing return type annotation for public function `perform_sort`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 18, - "endLine": 18, - "startColumn": 9, - "endColumn": 21, - "rule": "D102", - "category": "pydocstyle", - "message": "Missing docstring in public method", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 18, - "endLine": 18, - "startColumn": 28, - "endColumn": 31, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `arr`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 21, - "endLine": 21, - "startColumn": 9, - "endColumn": 21, - "rule": "ANN202", - "category": "flake8-annotations", - "message": "Missing return type annotation for private function `__merge_sort`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 21, - "endLine": 21, - "startColumn": 28, - "endColumn": 31, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `arr`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 21, - "endLine": 21, - "startColumn": 33, - "endColumn": 36, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `low`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 21, - "endLine": 21, - "startColumn": 38, - "endColumn": 42, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `high`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 30, - "endLine": 30, - "startColumn": 9, - "endColumn": 16, - "rule": "ANN202", - "category": "flake8-annotations", - "message": "Missing return type annotation for private function `__merge`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 30, - "endLine": 30, - "startColumn": 23, - "endColumn": 26, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `arr`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 30, - "endLine": 30, - "startColumn": 28, - "endColumn": 31, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `low`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 30, - "endLine": 30, - "startColumn": 33, - "endColumn": 36, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `mid`", - "priority": "error" - }, - { - "filePath": "/home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py", - "startLine": 30, - "endLine": 30, - "startColumn": 38, - "endColumn": 42, - "rule": "ANN001", - "category": "flake8-annotations", - "message": "Missing type annotation for function argument `high`", - "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 index 7fd9621dfc99..5eb5c0e64e03 100644 --- a/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif +++ b/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif @@ -9,791 +9,21 @@ { "physicalLocation": { "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" + "uri": "file:///usr/local/src/exercise/policy.py" }, "region": { - "endColumn": 1, - "endLine": 1, - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "Missing docstring in public module" - }, - "ruleId": "D100" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" - }, - "region": { - "endColumn": 14, - "endLine": 1, - "startColumn": 7, - "startLine": 1 - } - } - } - ], - "message": { - "text": "Missing docstring in public class" - }, - "ruleId": "D101" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" - }, - "region": { - "endColumn": 13, - "endLine": 5, - "startColumn": 9, - "startLine": 5 - } - } - } - ], - "message": { - "text": "Missing return type annotation for public function `sort`" - }, - "ruleId": "ANN201" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/context.py" - }, - "region": { - "endColumn": 13, - "endLine": 5, - "startColumn": 9, - "startLine": 5 - } - } - } - ], - "message": { - "text": "Missing docstring in public method" - }, - "ruleId": "D102" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 1, - "endLine": 1, - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "Missing docstring in public module" - }, - "ruleId": "D100" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 13, - "endLine": 4, - "startColumn": 7, - "startLine": 4 - } - } - } - ], - "message": { - "text": "Missing docstring in public class" - }, - "ruleId": "D101" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 17, - "endLine": 7, - "startColumn": 9, - "startLine": 7 - } - } - } - ], - "message": { - "text": "Missing return type annotation for special method `__init__`" - }, - "ruleId": "ANN204" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 17, - "endLine": 7, - "startColumn": 9, - "startLine": 7 - } - } - } - ], - "message": { - "text": "Missing docstring in `__init__`" - }, - "ruleId": "D107" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 31, - "endLine": 7, - "startColumn": 24, - "startLine": 7 - } - } - } - ], - "message": { - "text": "Missing type annotation for function argument `context`" - }, - "ruleId": "ANN001" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 18, - "endLine": 10, - "startColumn": 9, - "startLine": 10 - } - } - } - ], - "message": { - "text": "Missing return type annotation for public function `configure`" - }, - "ruleId": "ANN201" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 18, - "endLine": 10, - "startColumn": 9, - "startLine": 10 - } - } - } - ], - "message": { - "text": "Missing docstring in public method" - }, - "ruleId": "D102" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 18, - "endLine": 12, - "startColumn": 13, - "startLine": 12 - } - } - } - ], - "message": { - "text": "`print` found" - }, - "ruleId": "T201" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 63, - "endLine": 12, - "startColumn": 19, - "startLine": 12 - } - } - } - ], - "message": { - "text": "Single quotes found but double quotes preferred" - }, - "ruleId": "Q000" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 18, - "endLine": 15, - "startColumn": 13, - "startLine": 15 - } - } - } - ], - "message": { - "text": "`print` found" - }, - "ruleId": "T201" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/policy.py" - }, - "region": { - "endColumn": 73, - "endLine": 15, - "startColumn": 19, - "startLine": 15 - } - } - } - ], - "message": { - "text": "Single quotes found but double quotes preferred" - }, - "ruleId": "Q000" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/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:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" - }, - "region": { - "endColumn": 1, - "endLine": 1, - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "Missing docstring in public module" - }, - "ruleId": "D100" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" - }, - "region": { - "endColumn": 19, - "endLine": 4, - "startColumn": 7, - "startLine": 4 - } - } - } - ], - "message": { - "text": "Missing docstring in public class" - }, - "ruleId": "D101" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" - }, - "region": { - "endColumn": 21, - "endLine": 7, - "startColumn": 9, - "startLine": 7 - } - } - } - ], - "message": { - "text": "Missing return type annotation for public function `perform_sort`" - }, - "ruleId": "ANN201" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" - }, - "region": { - "endColumn": 21, - "endLine": 7, - "startColumn": 9, - "startLine": 7 - } - } - } - ], - "message": { - "text": "Missing docstring in public method" - }, - "ruleId": "D102" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sort_strategy.py" - }, - "region": { - "endColumn": 33, - "endLine": 7, - "startColumn": 28, - "startLine": 7 - } - } - } - ], - "message": { - "text": "Missing type annotation for function argument `array`" - }, - "ruleId": "ANN001" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 1, - "endLine": 1, - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "Missing docstring in public module" - }, - "ruleId": "D100" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 17, - "endLine": 4, - "startColumn": 7, - "startLine": 4 - } - } - } - ], - "message": { - "text": "Missing docstring in public class" - }, - "ruleId": "D101" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 21, - "endLine": 6, - "startColumn": 9, - "startLine": 6 - } - } - } - ], - "message": { - "text": "Missing return type annotation for public function `perform_sort`" - }, - "ruleId": "ANN201" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 21, - "endLine": 6, - "startColumn": 9, - "startLine": 6 - } - } - } - ], - "message": { - "text": "Missing docstring in public method" - }, - "ruleId": "D102" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 31, - "endLine": 6, - "startColumn": 28, - "startLine": 6 - } - } - } - ], - "message": { - "text": "Missing type annotation for function argument `arr`" - }, - "ruleId": "ANN001" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 16, - "endLine": 16, - "startColumn": 7, - "startLine": 16 - } - } - } - ], - "message": { - "text": "Missing docstring in public class" - }, - "ruleId": "D101" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 21, - "endLine": 18, - "startColumn": 9, - "startLine": 18 - } - } - } - ], - "message": { - "text": "Missing return type annotation for public function `perform_sort`" - }, - "ruleId": "ANN201" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 21, - "endLine": 18, - "startColumn": 9, - "startLine": 18 - } - } - } - ], - "message": { - "text": "Missing docstring in public method" - }, - "ruleId": "D102" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 31, - "endLine": 18, - "startColumn": 28, - "startLine": 18 - } - } - } - ], - "message": { - "text": "Missing type annotation for function argument `arr`" - }, - "ruleId": "ANN001" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 21, - "endLine": 21, - "startColumn": 9, - "startLine": 21 - } - } - } - ], - "message": { - "text": "Missing return type annotation for private function `__merge_sort`" - }, - "ruleId": "ANN202" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 31, - "endLine": 21, - "startColumn": 28, - "startLine": 21 - } - } - } - ], - "message": { - "text": "Missing type annotation for function argument `arr`" - }, - "ruleId": "ANN001" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 36, - "endLine": 21, - "startColumn": 33, - "startLine": 21 + "endColumn": 34, + "endLine": 1, + "startColumn": 1, + "startLine": 1 } } } ], "message": { - "text": "Missing type annotation for function argument `low`" + "text": "`from .sorting_algorithms import *` used; unable to detect undefined names" }, - "ruleId": "ANN001" + "ruleId": "F403" }, { "level": "error", @@ -801,87 +31,21 @@ { "physicalLocation": { "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + "uri": "file:///usr/local/src/exercise/policy.py" }, "region": { "endColumn": 42, - "endLine": 21, - "startColumn": 38, - "startLine": 21 - } - } - } - ], - "message": { - "text": "Missing type annotation for function argument `high`" - }, - "ruleId": "ANN001" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 16, - "endLine": 30, - "startColumn": 9, - "startLine": 30 - } - } - } - ], - "message": { - "text": "Missing return type annotation for private function `__merge`" - }, - "ruleId": "ANN202" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 26, - "endLine": 30, - "startColumn": 23, - "startLine": 30 - } - } - } - ], - "message": { - "text": "Missing type annotation for function argument `arr`" - }, - "ruleId": "ANN001" - }, - { - "level": "error", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" - }, - "region": { - "endColumn": 31, - "endLine": 30, - "startColumn": 28, - "startLine": 30 + "endLine": 11, + "startColumn": 40, + "startLine": 11 } } } ], "message": { - "text": "Missing type annotation for function argument `low`" + "text": "Magic value used in comparison, consider replacing `10` with a constant variable" }, - "ruleId": "ANN001" + "ruleId": "PLR2004" }, { "level": "error", @@ -889,21 +53,21 @@ { "physicalLocation": { "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + "uri": "file:///usr/local/src/exercise/policy.py" }, "region": { - "endColumn": 36, - "endLine": 30, - "startColumn": 33, - "startLine": 30 + "endColumn": 55, + "endLine": 13, + "startColumn": 46, + "startLine": 13 } } } ], "message": { - "text": "Missing type annotation for function argument `mid`" + "text": "`MergeSort` may be undefined, or defined from star imports" }, - "ruleId": "ANN001" + "ruleId": "F405" }, { "level": "error", @@ -911,21 +75,21 @@ { "physicalLocation": { "artifactLocation": { - "uri": "file:///home/user/src/Artemis/src/main/resources/templates/python/solution/sorting_algorithms.py" + "uri": "file:///usr/local/src/exercise/policy.py" }, "region": { - "endColumn": 42, - "endLine": 30, - "startColumn": 38, - "startLine": 30 + "endColumn": 56, + "endLine": 16, + "startColumn": 46, + "startLine": 16 } } } ], "message": { - "text": "Missing type annotation for function argument `high`" + "text": "`BubbleSort` may be undefined, or defined from star imports" }, - "ruleId": "ANN001" + "ruleId": "F405" } ], "tool": { @@ -933,158 +97,6 @@ "informationUri": "https://github.com/astral-sh/ruff", "name": "ruff", "rules": [ - { - "fullDescription": { - "text": "## What it does\nChecks that function arguments have type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the types of function arguments. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany provided arguments match expectation.\n\n## Example\n\n```python\ndef foo(x): ...\n```\n\nUse instead:\n\n```python\ndef foo(x: int): ...\n```\n" - }, - "help": { - "text": "Missing type annotation for function argument `{name}`" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/missing-type-function-argument", - "id": "ANN001", - "properties": { - "id": "ANN001", - "kind": "flake8-annotations", - "name": "missing-type-function-argument", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing type annotation for function argument `{name}`" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks that public functions and methods have return type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the return types of functions. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany returned values, and the types expected by callers, match expectation.\n\n## Example\n```python\ndef add(a, b):\n return a + b\n```\n\nUse instead:\n```python\ndef add(a: int, b: int) -> int:\n return a + b\n```\n" - }, - "help": { - "text": "Missing return type annotation for public function `{name}`" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function", - "id": "ANN201", - "properties": { - "id": "ANN201", - "kind": "flake8-annotations", - "name": "missing-return-type-undocumented-public-function", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing return type annotation for public function `{name}`" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks that private functions and methods have return type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the return types of functions. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany returned values, and the types expected by callers, match expectation.\n\n## Example\n```python\ndef _add(a, b):\n return a + b\n```\n\nUse instead:\n```python\ndef _add(a: int, b: int) -> int:\n return a + b\n```\n" - }, - "help": { - "text": "Missing return type annotation for private function `{name}`" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/missing-return-type-private-function", - "id": "ANN202", - "properties": { - "id": "ANN202", - "kind": "flake8-annotations", - "name": "missing-return-type-private-function", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing return type annotation for private function `{name}`" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks that \"special\" methods, like `__init__`, `__new__`, and `__call__`, have\nreturn type annotations.\n\n## Why is this bad?\nType annotations are a good way to document the return types of functions. They also\nhelp catch bugs, when used alongside a type checker, by ensuring that the types of\nany returned values, and the types expected by callers, match expectation.\n\nNote that type checkers often allow you to omit the return type annotation for\n`__init__` methods, as long as at least one argument has a type annotation. To\nopt in to this behavior, use the `mypy-init-return` setting in your `pyproject.toml`\nor `ruff.toml` file:\n\n```toml\n[tool.ruff.lint.flake8-annotations]\nmypy-init-return = true\n```\n\n## Example\n```python\nclass Foo:\n def __init__(self, x: int):\n self.x = x\n```\n\nUse instead:\n```python\nclass Foo:\n def __init__(self, x: int) -> None:\n self.x = x\n```\n" - }, - "help": { - "text": "Missing return type annotation for special method `{name}`" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/missing-return-type-special-method", - "id": "ANN204", - "properties": { - "id": "ANN204", - "kind": "flake8-annotations", - "name": "missing-return-type-special-method", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing return type annotation for special method `{name}`" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks for undocumented public module definitions.\n\n## Why is this bad?\nPublic modules should be documented via docstrings to outline their purpose\nand contents.\n\nGenerally, module docstrings should describe the purpose of the module and\nlist the classes, exceptions, functions, and other objects that are exported\nby the module, alongside a one-line summary of each.\n\nIf the module is a script, the docstring should be usable as its \"usage\"\nmessage.\n\nIf the codebase adheres to a standard format for module docstrings, follow\nthat format for consistency.\n\n## Example\n\n```python\nclass FasterThanLightError(ZeroDivisionError): ...\n\n\ndef calculate_speed(distance: float, time: float) -> float: ...\n```\n\nUse instead:\n\n```python\n\"\"\"Utility functions and classes for calculating speed.\n\nThis module provides:\n- FasterThanLightError: exception when FTL speed is calculated;\n- calculate_speed: calculate speed given distance and time.\n\"\"\"\n\n\nclass FasterThanLightError(ZeroDivisionError): ...\n\n\ndef calculate_speed(distance: float, time: float) -> float: ...\n```\n\n## Notebook behavior\nThis rule is ignored for Jupyter Notebooks.\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)\n" - }, - "help": { - "text": "Missing docstring in public module" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-module", - "id": "D100", - "properties": { - "id": "D100", - "kind": "pydocstyle", - "name": "undocumented-public-module", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing docstring in public module" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks for undocumented public class definitions.\n\n## Why is this bad?\nPublic classes should be documented via docstrings to outline their purpose\nand behavior.\n\nGenerally, a class docstring should describe the class's purpose and list\nits public attributes and methods.\n\nIf the codebase adheres to a standard format for class docstrings, follow\nthat format for consistency.\n\n## Example\n```python\nclass Player:\n def __init__(self, name: str, points: int = 0) -> None:\n self.name: str = name\n self.points: int = points\n\n def add_points(self, points: int) -> None:\n self.points += points\n```\n\nUse instead (in the NumPy docstring format):\n```python\nclass Player:\n \"\"\"A player in the game.\n\n Attributes\n ----------\n name : str\n The name of the player.\n points : int\n The number of points the player has.\n\n Methods\n -------\n add_points(points: int) -> None\n Add points to the player's score.\n \"\"\"\n\n def __init__(self, name: str, points: int = 0) -> None:\n self.name: str = name\n self.points: int = points\n\n def add_points(self, points: int) -> None:\n self.points += points\n```\n\nOr (in the Google docstring format):\n```python\nclass Player:\n \"\"\"A player in the game.\n\n Attributes:\n name: The name of the player.\n points: The number of points the player has.\n \"\"\"\n\n def __init__(self, name: str, points: int = 0) -> None:\n self.name: str = name\n self.points: int = points\n\n def add_points(self, points: int) -> None:\n self.points += points\n```\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)\n" - }, - "help": { - "text": "Missing docstring in public class" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-class", - "id": "D101", - "properties": { - "id": "D101", - "kind": "pydocstyle", - "name": "undocumented-public-class", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing docstring in public class" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks for undocumented public method definitions.\n\n## Why is this bad?\nPublic methods should be documented via docstrings to outline their purpose\nand behavior.\n\nGenerally, a method docstring should describe the method's behavior,\narguments, side effects, exceptions, return values, and any other\ninformation that may be relevant to the user.\n\nIf the codebase adheres to a standard format for method docstrings, follow\nthat format for consistency.\n\n## Example\n```python\nclass Cat(Animal):\n def greet(self, happy: bool = True):\n if happy:\n print(\"Meow!\")\n else:\n raise ValueError(\"Tried to greet an unhappy cat.\")\n```\n\nUse instead (in the NumPy docstring format):\n```python\nclass Cat(Animal):\n def greet(self, happy: bool = True):\n \"\"\"Print a greeting from the cat.\n\n Parameters\n ----------\n happy : bool, optional\n Whether the cat is happy, is True by default.\n\n Raises\n ------\n ValueError\n If the cat is not happy.\n \"\"\"\n if happy:\n print(\"Meow!\")\n else:\n raise ValueError(\"Tried to greet an unhappy cat.\")\n```\n\nOr (in the Google docstring format):\n```python\nclass Cat(Animal):\n def greet(self, happy: bool = True):\n \"\"\"Print a greeting from the cat.\n\n Args:\n happy: Whether the cat is happy, is True by default.\n\n Raises:\n ValueError: If the cat is not happy.\n \"\"\"\n if happy:\n print(\"Meow!\")\n else:\n raise ValueError(\"Tried to greet an unhappy cat.\")\n```\n\n## Options\n- `lint.pydocstyle.ignore-decorators`\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)\n" - }, - "help": { - "text": "Missing docstring in public method" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-method", - "id": "D102", - "properties": { - "id": "D102", - "kind": "pydocstyle", - "name": "undocumented-public-method", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing docstring in public method" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks for public `__init__` method definitions that are missing\ndocstrings.\n\n## Why is this bad?\nPublic `__init__` methods are used to initialize objects. `__init__`\nmethods should be documented via docstrings to describe the method's\nbehavior, arguments, side effects, exceptions, and any other information\nthat may be relevant to the user.\n\nIf the codebase adheres to a standard format for `__init__` method docstrings,\nfollow that format for consistency.\n\n## Example\n```python\nclass City:\n def __init__(self, name: str, population: int) -> None:\n self.name: str = name\n self.population: int = population\n```\n\nUse instead:\n```python\nclass City:\n def __init__(self, name: str, population: int) -> None:\n \"\"\"Initialize a city with a name and population.\"\"\"\n self.name: str = name\n self.population: int = population\n```\n\n## Options\n- `lint.pydocstyle.ignore-decorators`\n\n## References\n- [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)\n- [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)\n- [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)\n- [Google Style Python Docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)\n" - }, - "help": { - "text": "Missing docstring in `__init__`" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/undocumented-public-init", - "id": "D107", - "properties": { - "id": "D107", - "kind": "pydocstyle", - "name": "undocumented-public-init", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Missing docstring in `__init__`" - } - }, { "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" @@ -1125,7 +137,7 @@ }, { "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[PEP 8]: https://peps.python.org/pep-0008/#constants\n" + "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" @@ -1141,50 +153,12 @@ "shortDescription": { "text": "Magic value used in comparison, consider replacing `{value}` with a constant variable" } - }, - { - "fullDescription": { - "text": "## What it does\nChecks for inline strings that use single quotes or double quotes,\ndepending on the value of the [`lint.flake8-quotes.inline-quotes`] option.\n\n## Why is this bad?\nConsistency is good. Use either single or double quotes for inline\nstrings, but be consistent.\n\n## Example\n```python\nfoo = 'bar'\n```\n\nAssuming `inline-quotes` is set to `double`, use instead:\n```python\nfoo = \"bar\"\n```\n\n## Options\n- `lint.flake8-quotes.inline-quotes`\n\n## Formatter compatibility\nWe recommend against using this rule alongside the [formatter]. The\nformatter enforces consistent quotes for inline strings, making the rule\nredundant.\n\n[formatter]: https://docs.astral.sh/ruff/formatter\n" - }, - "help": { - "text": "Single quotes found but double quotes preferred" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/bad-quotes-inline-string", - "id": "Q000", - "properties": { - "id": "Q000", - "kind": "flake8-quotes", - "name": "bad-quotes-inline-string", - "problem.severity": "error" - }, - "shortDescription": { - "text": "Single quotes found but double quotes preferred" - } - }, - { - "fullDescription": { - "text": "## What it does\nChecks for `print` statements.\n\n## Why is this bad?\n`print` statements are useful in some situations (e.g., debugging), but\nshould typically be omitted from production code. `print` statements can\nlead to the accidental inclusion of sensitive information in logs, and are\nnot configurable by clients, unlike `logging` statements.\n\n## Example\n```python\ndef add_numbers(a, b):\n print(f\"The sum of {a} and {b} is {a + b}\")\n return a + b\n```\n\nUse instead:\n```python\ndef add_numbers(a, b):\n return a + b\n```\n\n## Fix safety\nThis rule's fix is marked as unsafe, as it may remove `print` statements\nthat are used beyond debugging purposes.\n" - }, - "help": { - "text": "`print` found" - }, - "helpUri": "https://docs.astral.sh/ruff/rules/print", - "id": "T201", - "properties": { - "id": "T201", - "kind": "flake8-print", - "name": "print", - "problem.severity": "error" - }, - "shortDescription": { - "text": "`print` found" - } } ], - "version": "0.6.9" + "version": "0.8.0" } } } ], "version": "2.1.0" -} \ No newline at end of file +} From 3bcb8a2e1e38de164c071f38556d8a67607bc33b Mon Sep 17 00:00:00 2001 From: Marcel Gaupp Date: Thu, 5 Dec 2024 23:41:35 +0100 Subject: [PATCH 23/23] Add "Unknown" category --- .../config/StaticCodeAnalysisConfigurer.java | 3 +- .../static-code-analysis/expected/ruff.json | 11 +++++++ .../static-code-analysis/reports/ruff.sarif | 31 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) 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 703d6ffeb537..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 @@ -73,7 +73,8 @@ public class StaticCodeAnalysisConfigurer { "Perflint", "refurb", "pydoclint", - "Ruff-specific rules" + "Ruff-specific rules", + "Unknown" ); // @formatter:on 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 index d81f53f8106b..b5d22a4de679 100644 --- a/src/test/resources/test-data/static-code-analysis/expected/ruff.json +++ b/src/test/resources/test-data/static-code-analysis/expected/ruff.json @@ -44,6 +44,17 @@ "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 index 5eb5c0e64e03..363726778ea0 100644 --- a/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif +++ b/src/test/resources/test-data/static-code-analysis/reports/ruff.sarif @@ -90,6 +90,28 @@ "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": { @@ -153,6 +175,15 @@ "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"