Skip to content

Commit

Permalink
Undo applying to samples
Browse files Browse the repository at this point in the history
  • Loading branch information
rock3r committed Nov 1, 2023
1 parent a365321 commit d9d1bb0
Show file tree
Hide file tree
Showing 52 changed files with 1,466 additions and 1,467 deletions.
61 changes: 30 additions & 31 deletions buildSrc/src/main/kotlin/IdeaConfiguration.kt
Original file line number Diff line number Diff line change
@@ -1,47 +1,46 @@
import java.util.concurrent.atomic.AtomicBoolean
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.jvm.toolchain.JavaLanguageVersion
import java.util.concurrent.atomic.AtomicBoolean

enum class SupportedIJVersion {
IJ_232,
IJ_233
IJ_232,
IJ_233
}

private var warned = AtomicBoolean(false)

fun Project.supportedIJVersion(): SupportedIJVersion {
val prop =
kotlin
.runCatching {
rootProject.findProperty("supported.ij.version")?.toString()
?: localProperty("supported.ij.version")
}
.getOrNull()
val prop =
kotlin
.runCatching {
rootProject.findProperty("supported.ij.version")?.toString()
?: localProperty("supported.ij.version")
}
.getOrNull()

if (prop == null) {
if (!warned.getAndSet(true)) {
logger.warn(
"""
No 'supported.ij.version' property provided. Falling back to IJ 233.
It is recommended to provide it using local.properties file or -Psupported.ij.version to avoid unexpected behavior.
if (prop == null) {
if (!warned.getAndSet(true)) {
logger.warn(
"""
.trimIndent()
)
No 'supported.ij.version' property provided. Falling back to IJ 233.
It is recommended to provide it using local.properties file or -Psupported.ij.version to
avoid unexpected behavior.
""".trimIndent()
)
}
return SupportedIJVersion.IJ_233
}
return SupportedIJVersion.IJ_233
}

return when (prop) {
"232" -> SupportedIJVersion.IJ_232
"233" -> SupportedIJVersion.IJ_233
else ->
error(
"Invalid 'supported.ij.version' with value '$prop' is provided. " +
"It should be in set of these values: ('232', '233')"
)
}
return when (prop) {
"232" -> SupportedIJVersion.IJ_232
"233" -> SupportedIJVersion.IJ_233
else ->
error(
"Invalid 'supported.ij.version' with value '$prop' is provided. " +
"It should be one of these values: ('232', '233')"
)
}
}

fun Property<JavaLanguageVersion>.assign(version: Int) =
set(JavaLanguageVersion.of(version))
fun Property<JavaLanguageVersion>.assign(version: Int) = set(JavaLanguageVersion.of(version))
16 changes: 8 additions & 8 deletions buildSrc/src/main/kotlin/LocalProperties.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import java.util.Properties
import org.gradle.api.Project
import java.util.Properties

internal fun Project.localProperty(propertyName: String): String? {
val localPropertiesFile = rootProject.file("local.properties")
if (!localPropertiesFile.exists()) {
return null
}
val properties = Properties()
localPropertiesFile.inputStream().use { properties.load(it) }
return properties.getProperty(propertyName)
val localPropertiesFile = rootProject.file("local.properties")
if (!localPropertiesFile.exists()) {
return null
}
val properties = Properties()
localPropertiesFile.inputStream().use { properties.load(it) }
return properties.getProperty(propertyName)
}
88 changes: 47 additions & 41 deletions buildSrc/src/main/kotlin/MergeSarifTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,54 @@ import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction

private const val SARIF_SCHEMA =
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"

@CacheableTask
open class MergeSarifTask : SourceTask() {

init {
group = "verification"
}

@get:OutputFile
val mergedSarifPath: RegularFileProperty =
project.objects
.fileProperty()
.convention(project.layout.buildDirectory.file("reports/static-analysis.sarif"))

@TaskAction
fun merge() {
val json = Json { prettyPrint = true }

logger.lifecycle("Merging ${source.files.size} SARIF file(s)...")
logger.lifecycle(
source.files.joinToString("\n") { " * ~${it.path.removePrefix(project.rootDir.path)}" }
)

val merged =
SarifSchema210(
schema =
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
version = Version.The210,
runs =
source.files
.asSequence()
.filter { it.extension == "sarif" }
.map { file -> file.inputStream().use { json.decodeFromStream<SarifSchema210>(it) } }
.flatMap { report -> report.runs }
.groupBy { run -> run.tool.driver.guid ?: run.tool.driver.name }
.values
.asSequence()
.filter { it.isNotEmpty() }
.map { run -> run.first().copy(results = run.flatMap { it.results ?: emptyList() }) }
.toList()
)
logger.lifecycle("Merged SARIF file contains ${merged.runs.size} run(s)")
logger.info("Writing merged SARIF file to $mergedSarifPath...")
mergedSarifPath.asFile.get().outputStream().use { json.encodeToStream(merged, it) }
}
init {
group = "verification"
}

@get:OutputFile
val mergedSarifPath: RegularFileProperty =
project.objects
.fileProperty()
.convention(project.layout.buildDirectory.file("reports/static-analysis.sarif"))

@TaskAction
fun merge() {
val json = Json { prettyPrint = true }

logger.lifecycle("Merging ${source.files.size} SARIF file(s)...")
logger.lifecycle(
source.files.joinToString("\n") { " * ~${it.path.removePrefix(project.rootDir.path)}" }
)

val merged =
SarifSchema210(
schema = SARIF_SCHEMA,
version = Version.The210,
runs = source.files
.asSequence()
.filter { it.extension == "sarif" }
.map { file ->
file.inputStream().use { json.decodeFromStream<SarifSchema210>(it) }
}
.flatMap { report -> report.runs }
.groupBy { run -> run.tool.driver.guid ?: run.tool.driver.name }
.values
.asSequence()
.filter { it.isNotEmpty() }
.map { run ->
run.first().copy(results = run.flatMap { it.results ?: emptyList() })
}
.toList()
)

logger.lifecycle("Merged SARIF file contains ${merged.runs.size} run(s)")
logger.info("Writing merged SARIF file to $mergedSarifPath...")
mergedSarifPath.asFile.get().outputStream().use { json.encodeToStream(merged, it) }
}
}
153 changes: 76 additions & 77 deletions buildSrc/src/main/kotlin/ValidatePublicApiTask.kt
Original file line number Diff line number Diff line change
@@ -1,103 +1,102 @@
import java.io.File
import java.util.Stack
import org.gradle.api.GradleException
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.util.Stack

@CacheableTask
open class ValidatePublicApiTask : SourceTask() {

init {
group = "verification"
init {
group = "verification"

// The output is never really used, it is here for cacheability reasons only
outputs.file(project.layout.buildDirectory.file("apiValidationRun"))
}
// The output is never really used, it is here for cacheability reasons only
outputs.file(project.layout.buildDirectory.file("apiValidationRun"))
}

private val classFqnRegex = "public (?:\\w+ )*class (\\S+)\\b".toRegex()
private val classFqnRegex = "public (?:\\w+ )*class (\\S+)\\b".toRegex()

@Suppress(
"ConvertToStringTemplate"
) // The odd concatenation is needed because of $; escapes get confused
private val copyMethodRegex =
("public static synthetic fun copy(-\\w+)?" + "\\$" + "default\\b").toRegex()
@Suppress("ConvertToStringTemplate") // The odd concatenation is needed because of $; escapes get confused
private val copyMethodRegex =
("public static synthetic fun copy(-\\w+)?" + "\\$" + "default\\b").toRegex()

@TaskAction
fun validatePublicApi() {
logger.info("Validating ${source.files.size} API file(s)...")
@TaskAction
fun validatePublicApi() {
logger.info("Validating ${source.files.size} API file(s)...")

val violations = mutableMapOf<File, Set<String>>()
inputs.files.forEach { apiFile ->
logger.lifecycle("Validating public API from file ${apiFile.path}")
val violations = mutableMapOf<File, Set<String>>()
inputs.files.forEach { apiFile ->
logger.lifecycle("Validating public API from file ${apiFile.path}")

apiFile.useLines { lines ->
val actualDataClasses = findDataClasses(lines)
apiFile.useLines { lines ->
val actualDataClasses = findDataClasses(lines)

if (actualDataClasses.isNotEmpty()) {
violations[apiFile] = actualDataClasses
if (actualDataClasses.isNotEmpty()) {
violations[apiFile] = actualDataClasses
}
}
}

if (violations.isNotEmpty()) {
val message = buildString {
appendLine("Data classes found in public API.")
appendLine()

for ((file, dataClasses) in violations.entries) {
appendLine("In file ${file.path}:")
for (dataClass in dataClasses) {
appendLine(" * ${dataClass.replace("/", ".")}")
}
appendLine()
}
}

throw GradleException(message)
} else {
logger.lifecycle("No public API violations found.")
}
}
}

if (violations.isNotEmpty()) {
val message = buildString {
appendLine("Data classes found in public API.")
appendLine()

for ((file, dataClasses) in violations.entries) {
appendLine("In file ${file.path}:")
for (dataClass in dataClasses) {
appendLine(" * ${dataClass.replace("/", ".")}")
}
appendLine()
private fun findDataClasses(lines: Sequence<String>): Set<String> {
val currentClassStack = Stack<String>()
val dataClasses = mutableMapOf<String, DataClassInfo>()

for (line in lines) {
if (line.isBlank()) continue

val matchResult = classFqnRegex.find(line)
if (matchResult != null) {
val classFqn = matchResult.groupValues[1]
currentClassStack.push(classFqn)
continue
}

if (line.contains("}")) {
currentClassStack.pop()
continue
}

val fqn = currentClassStack.peek()
if (copyMethodRegex.find(line) != null) {
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) }
info.hasCopyMethod = true
} else if (line.contains("public static final synthetic fun box-impl")) {
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) }
info.isLikelyValueClass = true
}
}
}

throw GradleException(message)
} else {
logger.lifecycle("No public API violations found.")
val actualDataClasses =
dataClasses.filterValues { it.hasCopyMethod && !it.isLikelyValueClass }
.keys
return actualDataClasses
}
}

private fun findDataClasses(lines: Sequence<String>): Set<String> {
val currentClassStack = Stack<String>()
val dataClasses = mutableMapOf<String, DataClassInfo>()

for (line in lines) {
if (line.isBlank()) continue

val matchResult = classFqnRegex.find(line)
if (matchResult != null) {
val classFqn = matchResult.groupValues[1]
currentClassStack.push(classFqn)
continue
}

if (line.contains("}")) {
currentClassStack.pop()
continue
}

val fqn = currentClassStack.peek()
if (copyMethodRegex.find(line) != null) {
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) }
info.hasCopyMethod = true
} else if (line.contains("public static final synthetic fun box-impl")) {
val info = dataClasses.getOrPut(fqn) { DataClassInfo(fqn) }
info.isLikelyValueClass = true
}
}

val actualDataClasses =
dataClasses.filterValues { it.hasCopyMethod && !it.isLikelyValueClass }.keys
return actualDataClasses
}
}

@Suppress("DataClassShouldBeImmutable") // Only used in a loop, saves memory and is faster
private data class DataClassInfo(
val fqn: String,
var hasCopyMethod: Boolean = false,
var isLikelyValueClass: Boolean = false,
val fqn: String,
var hasCopyMethod: Boolean = false,
var isLikelyValueClass: Boolean = false,
)
9 changes: 8 additions & 1 deletion buildSrc/src/main/kotlin/jewel-check-public-api.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
plugins {
id("org.jetbrains.kotlinx.binary-compatibility-validator")
id("dev.drewhamilton.poko")
kotlin("jvm")
}

apiValidation {
Expand All @@ -14,7 +15,13 @@ apiValidation {
nonPublicMarkers.add("org.jetbrains.jewel.InternalJewelApi")
}

poko { pokoAnnotation = "org.jetbrains.jewel.foundation.GenerateDataFunctions" }
poko {
pokoAnnotation = "org.jetbrains.jewel.foundation.GenerateDataFunctions"
}

kotlin {
explicitApi()
}

tasks {
val validatePublicApi =
Expand Down
Loading

0 comments on commit d9d1bb0

Please sign in to comment.