-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support GitLab Code Quality report format
- Loading branch information
Showing
6 changed files
with
205 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
src/main/scala/com/sksamuel/scapegoat/io/GitlabCodeQualityReportWriter.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package com.sksamuel.scapegoat.io | ||
import com.sksamuel.scapegoat.{Feedback, Levels, Warning} | ||
|
||
import java.nio.charset.StandardCharsets | ||
import java.security.MessageDigest | ||
|
||
/** | ||
* Supports GitLab Code Quality report format. | ||
* | ||
* https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool | ||
*/ | ||
object GitlabCodeQualityReportWriter extends ReportWriter { | ||
|
||
override protected def fileName: String = "scapegoat-gitlab.json" | ||
|
||
override protected def generate(feedback: Feedback): String = | ||
toCodeQualityElements(feedback.warningsWithMinimalLevel, sys.env.get("CI_PROJECT_DIR")) | ||
.map(_.toJsonArrayElement) | ||
.mkString("[", ",", "]") | ||
|
||
def toCodeQualityElements( | ||
warnings: Seq[Warning], | ||
gitlabBuildDir: Option[String] | ||
): Seq[CodeQualityReportElement] = warnings.map { warning => | ||
// Stable hash for the same warning. | ||
// Avoids moving code blocks around from causing "new" detecions. | ||
val fingerprintRaw = warning.sourceFileNormalized + warning.snippet.getOrElse(warning.line.toString) | ||
val fingerprint = MessageDigest | ||
.getInstance("MD5") | ||
.digest(fingerprintRaw.getBytes(StandardCharsets.UTF_8)) | ||
.map("%02x".format(_)) | ||
.mkString | ||
|
||
val severity = warning.level match { | ||
case Levels.Error => CriticalSeverity | ||
case Levels.Warning => MinorSeverity | ||
case Levels.Info => InfoSeverity | ||
case _ => InfoSeverity | ||
} | ||
|
||
val gitlabCiNormalizedPath = gitlabBuildDir | ||
.map { buildDir => | ||
val fullBuildDir = if (buildDir.endsWith("/")) buildDir else s"$buildDir/" | ||
val file = warning.sourceFileFull | ||
if (file.startsWith(fullBuildDir)) file.drop(fullBuildDir.length) else file | ||
} | ||
.getOrElse(warning.sourceFileFull) | ||
|
||
val textStart = if (warning.explanation.startsWith(warning.text)) { | ||
"" | ||
} else { | ||
if (warning.text.endsWith(".")) { | ||
warning.text + " " | ||
} else { | ||
warning.text + ". " | ||
} | ||
} | ||
val description = s"$textStart${warning.explanation}" | ||
|
||
CodeQualityReportElement( | ||
description = description, | ||
checkName = warning.inspection, | ||
severity = severity, | ||
location = Location(gitlabCiNormalizedPath, Lines(warning.line)), | ||
fingerprint = fingerprint | ||
) | ||
} | ||
} | ||
|
||
sealed trait CodeClimateSeverity { | ||
val name: String | ||
} | ||
|
||
case object InfoSeverity extends CodeClimateSeverity { | ||
override val name: String = "info" | ||
} | ||
|
||
case object MinorSeverity extends CodeClimateSeverity { | ||
override val name: String = "minor" | ||
} | ||
|
||
case object CriticalSeverity extends CodeClimateSeverity { | ||
override val name: String = "critical" | ||
} | ||
|
||
case class Location(path: String, lines: Lines) | ||
|
||
case class Lines(begin: Int) | ||
|
||
case class CodeQualityReportElement( | ||
description: String, | ||
checkName: String, | ||
severity: CodeClimateSeverity, | ||
location: Location, | ||
fingerprint: String | ||
) { | ||
|
||
// Manual templating is a bit silly but avoids a dependency on a potentially conflicting json library. | ||
def toJsonArrayElement: String = | ||
s""" | ||
| { | ||
| "description": "${description.replace("\"", "\\\"")}", | ||
| "check_name": "$checkName", | ||
| "fingerprint": "$fingerprint", | ||
| "severity": "${severity.name}", | ||
| "location": { | ||
| "path": "${location.path}", | ||
| "lines": { | ||
| "begin": ${location.lines.begin} | ||
| } | ||
| } | ||
| }""".stripMargin | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
src/test/scala/com/sksamuel/scapegoat/io/GitlabCodeQualityReportWriterTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.sksamuel.scapegoat.io | ||
|
||
import com.sksamuel.scapegoat.{Levels, Warning} | ||
import org.scalatest.freespec.AnyFreeSpec | ||
import org.scalatest.matchers.should.Matchers | ||
|
||
class GitlabCodeQualityReportWriterTest extends AnyFreeSpec with Matchers { | ||
|
||
"GitlabCodeQualityReportWriter" - { | ||
|
||
"should transform feedback" in { | ||
val warnings = Seq( | ||
Warning( | ||
"Use of Option.get", | ||
13, | ||
Levels.Error, | ||
"/home/johnnei/git/scapegoat/src/main/scala/com/sksamuel/File.scala", | ||
"com.sksamuel.File.scala", | ||
Some("File.this.d.get"), | ||
"Using Option.get defeats the purpose", | ||
"com.sksamuel.scapegoat.inspections.option.OptionGet" | ||
) | ||
) | ||
|
||
val report = GitlabCodeQualityReportWriter | ||
.toCodeQualityElements(warnings, Some("/home/johnnei/git/scapegoat")) | ||
report should be( | ||
Seq( | ||
CodeQualityReportElement( | ||
"Use of Option.get. Using Option.get defeats the purpose", | ||
"com.sksamuel.scapegoat.inspections.option.OptionGet", | ||
CriticalSeverity, | ||
Location("src/main/scala/com/sksamuel/File.scala", Lines(13)), | ||
"909b14c15a3a3891659251f133058264" | ||
) | ||
) | ||
) | ||
} | ||
|
||
"should transform feedback without duplicate text" in { | ||
val warnings = Seq( | ||
Warning( | ||
"List.size is O(n)", | ||
13, | ||
Levels.Info, | ||
"/home/johnnei/git/scapegoat/src/main/scala/com/sksamuel/File.scala", | ||
"com.sksamuel.File.scala", | ||
None, | ||
"List.size is O(n). Consider using...", | ||
"com.sksamuel.scapegoat.inspections.collections.ListSize" | ||
) | ||
) | ||
|
||
val report = GitlabCodeQualityReportWriter | ||
.toCodeQualityElements(warnings, Some("/home/johnnei/git/scapegoat")) | ||
report should be( | ||
Seq( | ||
CodeQualityReportElement( | ||
"List.size is O(n). Consider using...", | ||
"com.sksamuel.scapegoat.inspections.collections.ListSize", | ||
InfoSeverity, | ||
Location("src/main/scala/com/sksamuel/File.scala", Lines(13)), | ||
"f79bc3223909939407272a1db37a6d17" | ||
) | ||
) | ||
) | ||
} | ||
} | ||
|
||
} |