-
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 (#803)
* Support GitLab Code Quality report format * clean up scapegoat warnings and reduce instance creation
- Loading branch information
Showing
7 changed files
with
220 additions
and
21 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
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
119 changes: 119 additions & 0 deletions
119
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,119 @@ | ||
package com.sksamuel.scapegoat.io | ||
|
||
import java.nio.charset.StandardCharsets | ||
import java.security.MessageDigest | ||
|
||
import com.sksamuel.scapegoat.{Feedback, Levels, Warning} | ||
|
||
/** | ||
* 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 = { | ||
val md5Digest = MessageDigest.getInstance("MD5") | ||
toCodeQualityElements(feedback.warningsWithMinimalLevel, sys.env.get("CI_PROJECT_DIR"), md5Digest) | ||
.map(_.toJsonArrayElement) | ||
.mkString("[", ",", "]") | ||
} | ||
|
||
private[io] def toCodeQualityElements( | ||
warnings: Seq[Warning], | ||
gitlabBuildDir: Option[String], | ||
messageDigest: MessageDigest | ||
): 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) | ||
|
||
messageDigest.reset() | ||
messageDigest.update(fingerprintRaw.getBytes(StandardCharsets.UTF_8)) | ||
val fingerprint = messageDigest | ||
.digest() | ||
.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" | ||
} | ||
|
||
final case class Location(path: String, lines: Lines) | ||
|
||
final case class Lines(begin: Int) | ||
|
||
final 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
66 changes: 66 additions & 0 deletions
66
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,66 @@ | ||
package com.sksamuel.scapegoat.io | ||
|
||
import java.security.MessageDigest | ||
|
||
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" | ||
), | ||
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"), | ||
MessageDigest.getInstance("MD5") | ||
) | ||
report should be( | ||
Seq( | ||
CodeQualityReportElement( | ||
// Extra dot after warning text to improve readability | ||
"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" | ||
), | ||
CodeQualityReportElement( | ||
// Warning text is trimmed to avoid duplicate text | ||
"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" | ||
) | ||
) | ||
) | ||
} | ||
} | ||
|
||
} |